Compare commits

..

22 Commits

Author SHA1 Message Date
97cf624440 fix(lot assignment): removed the return i had oops 2025-04-23 15:28:07 -05:00
e0df6b8cd7 feat(orders): energizer orders in 2025-04-23 15:27:28 -05:00
c248cc0129 test(forecast): loreal forecast starting 2025-04-23 15:27:09 -05:00
57d3727f49 refactor(profile): changed to patch vs posting 2025-04-23 15:25:46 -05:00
5a48dcf553 refactor(profile updates): added a link to the profile change email 2025-04-23 15:23:33 -05:00
eac7444038 fix(register): typo 2025-04-23 15:21:56 -05:00
b290b21ca2 feat(lst): removed v1 from the update 2025-04-23 15:20:54 -05:00
86905b591b feat(password change): added in password link to email and change in lst 2025-04-23 15:20:35 -05:00
28b050859a feat(dm): standard forecast and orders in 2025-04-23 15:19:34 -05:00
3c9e627021 feat(dm): buttons to the nav bar if customs are made 2025-04-23 15:18:58 -05:00
7152e72822 feat(registerpage): added in but hidden 2025-04-23 15:18:21 -05:00
a03130a961 feat(build): removed v1 from being built in 2025-04-23 15:17:31 -05:00
aea06c12a0 chore(release): 2.16.1 2025-04-23 15:14:45 -05:00
4f1d83137b ci(release): bump build number to 297 2025-04-23 14:17:20 -05:00
72a3292633 ci(release): bump build number to 296 2025-04-23 13:52:11 -05:00
174a6c0adc ci(release): bump build number to 295 2025-04-23 12:37:31 -05:00
556aaa381d ci(release): bump build number to 294 2025-04-22 19:04:17 -05:00
5f7915b81f ci(release): bump build number to 293 2025-04-22 18:55:59 -05:00
613a486160 ci(release): bump build number to 292 2025-04-22 16:23:39 -05:00
caccaf143c ci(release): bump build number to 291 2025-04-22 14:44:17 -05:00
2a7eb26e6b ci(release): bump build number to 290 2025-04-22 13:06:38 -05:00
844f337f6c ci(release): bump build number to 289 2025-04-22 11:55:55 -05:00
29 changed files with 1480 additions and 449 deletions

1
.gitignore vendored
View File

@@ -150,3 +150,4 @@ dist
.pnp.*
backend-0.1.3.zip
BulkForecastTemplate

View File

@@ -1,5 +1,7 @@
# All CHanges to LST can be found below.
### [2.16.1](https://git.tuffraid.net/cowch/lstV2/compare/v2.16.0...v2.16.1) (2025-04-23)
## [2.16.0](https://git.tuffraid.net/cowch/lstV2/compare/v2.15.0...v2.16.0) (2025-04-22)

View File

@@ -0,0 +1,213 @@
import { LstCard } from "../extendedUI/LstCard";
import { CardHeader } from "../ui/card";
import { toast } from "sonner";
import { Label } from "../ui/label";
import { Input } from "../ui/input";
import { Button } from "../ui/button";
import { useForm } from "@tanstack/react-form";
import { Separator } from "../ui/separator";
import { useNavigate } from "@tanstack/react-router";
import { useState } from "react";
import axios from "axios";
export default function RegisterForm() {
const navigate = useNavigate();
const [registering, setRegistering] = useState(false);
const form = useForm({
defaultValues: {
username: "",
password: "",
email: "",
},
onSubmit: async ({ value }) => {
setRegistering(true);
try {
const res = await axios.post("/api/auth/register", value);
if (res.data.success) {
navigate({ to: "/login" });
form.reset();
toast.success(
`${value.username} was just created please login`
);
setRegistering(false);
}
if (!res.data.success) {
toast.error(res.data.message);
setRegistering(false);
}
} catch (error) {
//console.log(error);
toast.error("There was an error registering");
setRegistering(false);
}
},
});
return (
<div className="ml-[25%]">
<LstCard className="p-3 w-96">
<CardHeader>
<div>
<p className="text-2xl">Login to register</p>
</div>
</CardHeader>
<hr className="rounded"></hr>
<form
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<Separator />
<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" className="mb-2">
Username
</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>
);
}}
/>
<Separator />
<form.Field
name="email"
validators={{
// We can choose between form-wide and field-specific validators
onChange: ({ value }) =>
value.length > 3
? undefined
: "You must enter a valid email",
}}
children={(field) => {
return (
<div className="m-2 min-w-48 max-w-96 p-2">
<Label htmlFor="email" className="mb-2">
Alpla Email
</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>
);
}}
/>
<Separator className="m-2" />
<p>
Your password Should be your windows password, as this
is how you will interact with alplaprod
</p>
<form.Field
name="password"
// We can choose between form-wide and field-specific validators
validators={{
onChangeAsyncDebounceMs: 500,
onChangeAsync: ({ value }) => {
// if (
// window.location.pathname.includes(
// "/users"
// ) &&
// value.length === 0
// ) {
// return;
// }
if (value.length < 4) {
return "Password must be at least 4 characters long.";
}
if (!/[A-Z]/.test(value)) {
return "Password must contain at least one uppercase letter.";
}
if (!/[a-z]/.test(value)) {
return "Password must contain at least one lower case letter.";
}
if (!/[0-9]/.test(value)) {
return "Password must contain at least one number.";
}
if (
!/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(
value
)
) {
return "Password must contain at least one special character.";
}
},
}}
children={(field) => {
return (
<div className="m-2 min-w-48 max-w-96 p-2">
<Label htmlFor="password1" className="mb-2">
Password
</Label>
<Input
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
type="password"
onChange={(e) =>
field.handleChange(e.target.value)
}
/>
{field.state.meta.errors.length ? (
<em>
{field.state.meta.errors.join(",")}
</em>
) : null}
</div>
);
}}
/>
<div className="mt-4 ml-4 flex justify-end">
<Button
type="submit"
onClick={form.handleSubmit}
disabled={registering}
>
Register
</Button>
</div>
</form>
</LstCard>
</div>
);
}

View File

@@ -1,78 +1,23 @@
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import OrderImport from "./OrderImport";
import StandardOrderTemplate from "./StandardOrderTemplate";
import { Button } from "@/components/ui/button";
import StandardForecastTemplate from "./StandardForecastTemplate";
import ForecastImport from "./ForecastImport";
import { useSettingStore } from "@/lib/store/useSettings";
export default function DMButtons() {
const { settings } = useSettingStore();
const plantToken = settings.filter((n) => n.name === "plantToken");
console.log(plantToken);
//console.log(plantToken);
return (
<div className="flex flex-row-reverse">
<div className="m-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>Standard DM</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>
Standard templates and imports
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>
<StandardOrderTemplate />
</DropdownMenuItem>
<DropdownMenuItem>
<OrderImport
fileType={"standard"}
name={"Standard Order Import"}
/>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<StandardForecastTemplate />
</DropdownMenuItem>
<DropdownMenuItem>
<ForecastImport
fileType={"standard"}
name={"Standard Forecast Import"}
/>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="flex flex-row-reverse gap-1">
{plantToken[0]?.value === "usday1" && (
<div className="m-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>Dayton Customs</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>
Custom import templates
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>
<OrderImport
fileType={"abbott"}
name={"Abbott truck list"}
/>
</DropdownMenuItem>
<DropdownMenuSeparator />
</DropdownMenuContent>
</DropdownMenu>
<div className="flex flex-row gap-2">
<OrderImport
fileType={"abbott"}
name={"Abbott truck list"}
/>
<OrderImport
fileType={"energizer"}
name={"Energizer Truck List"}
/>
</div>
)}
</div>

View File

@@ -18,7 +18,7 @@ export default function ForecastImport(props: any) {
// create the form data with the correct fileType
const formData = new FormData();
formData.append("postOrders", e.target.files[0]);
formData.append("postForecast", e.target.files[0]);
formData.append("fileType", props.fileType); // extra field
// console.log(formData);
@@ -33,10 +33,13 @@ export default function ForecastImport(props: any) {
},
}
);
console.log("Upload successful:", response.data);
toast.success(
"File Uploaded, please validate processing in alplaprod 2.0"
);
//console.log("Upload successful:", response.data);
toast.success(response?.data?.message);
fileInputRef.current.value = null;
setPosting(false);
// toast.success(
// "File Uploaded, please validate processing in alplaprod 2.0"
// );
setPosting(false);
} catch (error) {
console.log(error);

View File

@@ -20,7 +20,6 @@ export default function OrderImport(props: any) {
const formData = new FormData();
formData.append("postOrders", e.target.files[0]);
formData.append("fileType", props.fileType); // extra field
// console.log(formData);
try {
const response = await axios.post(
@@ -33,10 +32,9 @@ export default function OrderImport(props: any) {
},
}
);
console.log("Upload successful:", response.data);
toast.success(
"File Uploaded, please validate processing in alplaprod 2.0"
);
//console.log("Upload successful:", response.data);
toast.success(response?.data?.message);
fileInputRef.current.value = null;
setPosting(false);
} catch (error) {
console.log(error);

View File

@@ -1,5 +1,9 @@
import { LstCard } from "@/components/extendedUI/LstCard";
import { Separator } from "@/components/ui/separator";
import OrderImport from "./OrderImport";
import StandardOrderTemplate from "./StandardOrderTemplate";
import ForecastImport from "./ForecastImport";
import StandardForecastTemplate from "./StandardForecastTemplate";
export default function DmPage() {
return (
@@ -9,6 +13,13 @@ export default function DmPage() {
<h4 className="text-center underline text-2xl">
Simple instructions for creating/updating orders
</h4>
<div className="flex flex-row gap-3 m-1">
<OrderImport
fileType={"standard"}
name={"Standard Order Import"}
/>
<StandardOrderTemplate />
</div>
<Separator />
</div>
@@ -36,6 +47,7 @@ export default function DmPage() {
<h4 className="text-center underline text-2xl">
Some notes to consider
</h4>
<ul className="list-disc mr-2">
<li>
No longer need to add in the invoice id, we take the
@@ -80,7 +92,14 @@ export default function DmPage() {
<h4 className="text-center underline text-2xl">
Simple instructions for creating forecast
</h4>
<Separator className="my-4" />
<div className="flex flex-row gap-3 m-1">
<ForecastImport
fileType={"standard"}
name={"Standard Forecast Import"}
/>
<StandardForecastTemplate />
<Separator className="my-4" />
</div>
</div>
<div className="m-5">
<ul className="list-disc mr-2">

View File

@@ -0,0 +1,193 @@
import { useForm } from "@tanstack/react-form";
import { useNavigate } from "@tanstack/react-router";
import axios from "axios";
import { useState } from "react";
import { toast } from "sonner";
import { LstCard } from "../extendedUI/LstCard";
import { CardContent, CardHeader } from "../ui/card";
import { Label } from "../ui/label";
import { Input } from "../ui/input";
import { Button } from "../ui/button";
export default function PasswordChange() {
const [saving, setSaving] = useState(false);
const token = localStorage.getItem("auth_token");
const navigate = useNavigate();
const form = useForm({
defaultValues: {
password: "",
confirmPassword: "",
},
onSubmit: async ({ value }) => {
setSaving(true);
try {
const res = await axios.patch("/api/auth/profile", value, {
headers: { Authorization: `Bearer ${token}` },
});
//console.log(res.data);
if (res.data.success) {
localStorage.removeItem("auth_token");
navigate({ to: "/login" });
form.reset();
toast.success(`Your password was just updated.`);
setSaving(false);
}
if (!res.data.success) {
toast.error(res.data.message);
setSaving(false);
}
} catch (error) {
console.log(error);
toast.error("There was an error updating your password");
setSaving(false);
}
},
});
return (
<div>
<LstCard className="w-96 ml-7">
<CardHeader>
<p>Password Change Form</p>
</CardHeader>
<CardContent>
<form
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<form.Field
name="password"
// We can choose between form-wide and field-specific validators
validators={{
onChangeAsyncDebounceMs: 500,
onChangeAsync: ({ value }) => {
// if (
// window.location.pathname.includes(
// "/users"
// ) &&
// value.length === 0
// ) {
// return;
// }
if (value.length < 4) {
return "Password must be at least 4 characters long.";
}
if (!/[A-Z]/.test(value)) {
return "Password must contain at least one uppercase letter.";
}
if (!/[a-z]/.test(value)) {
return "Password must contain at least one lower case letter.";
}
if (!/[0-9]/.test(value)) {
return "Password must contain at least one number.";
}
if (
!/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(
value
)
) {
return "Password must contain at least one special character.";
}
},
}}
children={(field) => {
return (
<div className="m-2 min-w-48 max-w-96 p-2">
<Label
htmlFor="password1"
className="mb-2"
>
Password
</Label>
<Input
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
type="password"
onChange={(e) =>
field.handleChange(
e.target.value
)
}
/>
{field.state.meta.errors.length ? (
<em>
{field.state.meta.errors.join(
","
)}
</em>
) : null}
</div>
);
}}
/>
<form.Field
name="confirmPassword"
validators={{
onChange: ({ value, fieldApi }) => {
const password =
fieldApi.form.getFieldValue("password");
if (value !== password) {
return "Passwords do not match.";
}
},
}}
>
{(field) => {
return (
<div className="m-2 min-w-48 max-w-96 p-2">
<Label
htmlFor="confirmPassword"
className="mb-2"
>
Confirm Password
</Label>
<Input
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
type="password"
onChange={(e) =>
field.handleChange(
e.target.value
)
}
/>
{field.state.meta.errors.length ? (
<em>
{field.state.meta.errors.join(
", "
)}
</em>
) : null}
</div>
);
}}
</form.Field>
<div className="mt-4 ml-4 flex justify-end">
<Button
type="submit"
onClick={form.handleSubmit}
disabled={saving}
>
Save
</Button>
</div>
</form>
</CardContent>
</LstCard>
</div>
);
}

View File

@@ -11,6 +11,7 @@
// Import Routes
import { Route as rootRoute } from './routes/__root'
import { Route as RegisterImport } from './routes/register'
import { Route as LoginImport } from './routes/login'
import { Route as ChangelogImport } from './routes/changelog'
import { Route as AboutImport } from './routes/about'
@@ -27,6 +28,7 @@ import { Route as AdminSettingsImport } from './routes/_admin/settings'
import { Route as AdminServersImport } from './routes/_admin/servers'
import { Route as AdminNotificationMGTImport } from './routes/_admin/notificationMGT'
import { Route as AdminModulesImport } from './routes/_admin/modules'
import { Route as userPasswordChangeImport } from './routes/(user)/passwordChange'
import { Route as ocmeCyclecountIndexImport } from './routes/(ocme)/cyclecount/index'
import { Route as logisticsSiloAdjustmentsIndexImport } from './routes/(logistics)/siloAdjustments/index'
import { Route as logisticsOpenOrdersIndexImport } from './routes/(logistics)/openOrders/index'
@@ -40,6 +42,12 @@ import { Route as logisticsSiloAdjustmentsCommentCommentImport } from './routes/
// Create/Update Routes
const RegisterRoute = RegisterImport.update({
id: '/register',
path: '/register',
getParentRoute: () => rootRoute,
} as any)
const LoginRoute = LoginImport.update({
id: '/login',
path: '/login',
@@ -133,6 +141,12 @@ const AdminModulesRoute = AdminModulesImport.update({
getParentRoute: () => AdminRoute,
} as any)
const userPasswordChangeRoute = userPasswordChangeImport.update({
id: '/(user)/passwordChange',
path: '/passwordChange',
getParentRoute: () => rootRoute,
} as any)
const ocmeCyclecountIndexRoute = ocmeCyclecountIndexImport.update({
id: '/(ocme)/cyclecount/',
path: '/cyclecount/',
@@ -252,6 +266,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof LoginImport
parentRoute: typeof rootRoute
}
'/register': {
id: '/register'
path: '/register'
fullPath: '/register'
preLoaderRoute: typeof RegisterImport
parentRoute: typeof rootRoute
}
'/(user)/passwordChange': {
id: '/(user)/passwordChange'
path: '/passwordChange'
fullPath: '/passwordChange'
preLoaderRoute: typeof userPasswordChangeImport
parentRoute: typeof rootRoute
}
'/_admin/modules': {
id: '/_admin/modules'
path: '/modules'
@@ -438,6 +466,8 @@ export interface FileRoutesByFullPath {
'/about': typeof AboutRoute
'/changelog': typeof ChangelogRoute
'/login': typeof LoginRoute
'/register': typeof RegisterRoute
'/passwordChange': typeof userPasswordChangeRoute
'/modules': typeof AdminModulesRoute
'/notificationMGT': typeof AdminNotificationMGTRoute
'/servers': typeof AdminServersRoute
@@ -465,6 +495,8 @@ export interface FileRoutesByTo {
'/about': typeof AboutRoute
'/changelog': typeof ChangelogRoute
'/login': typeof LoginRoute
'/register': typeof RegisterRoute
'/passwordChange': typeof userPasswordChangeRoute
'/modules': typeof AdminModulesRoute
'/notificationMGT': typeof AdminNotificationMGTRoute
'/servers': typeof AdminServersRoute
@@ -495,6 +527,8 @@ export interface FileRoutesById {
'/about': typeof AboutRoute
'/changelog': typeof ChangelogRoute
'/login': typeof LoginRoute
'/register': typeof RegisterRoute
'/(user)/passwordChange': typeof userPasswordChangeRoute
'/_admin/modules': typeof AdminModulesRoute
'/_admin/notificationMGT': typeof AdminNotificationMGTRoute
'/_admin/servers': typeof AdminServersRoute
@@ -524,6 +558,8 @@ export interface FileRouteTypes {
| '/about'
| '/changelog'
| '/login'
| '/register'
| '/passwordChange'
| '/modules'
| '/notificationMGT'
| '/servers'
@@ -550,6 +586,8 @@ export interface FileRouteTypes {
| '/about'
| '/changelog'
| '/login'
| '/register'
| '/passwordChange'
| '/modules'
| '/notificationMGT'
| '/servers'
@@ -578,6 +616,8 @@ export interface FileRouteTypes {
| '/about'
| '/changelog'
| '/login'
| '/register'
| '/(user)/passwordChange'
| '/_admin/modules'
| '/_admin/notificationMGT'
| '/_admin/servers'
@@ -608,6 +648,8 @@ export interface RootRouteChildren {
AboutRoute: typeof AboutRoute
ChangelogRoute: typeof ChangelogRoute
LoginRoute: typeof LoginRoute
RegisterRoute: typeof RegisterRoute
userPasswordChangeRoute: typeof userPasswordChangeRoute
OcpIndexRoute: typeof OcpIndexRoute
logisticsSiloAdjustmentsHistRoute: typeof logisticsSiloAdjustmentsHistRoute
logisticsDmIndexRoute: typeof logisticsDmIndexRoute
@@ -628,6 +670,8 @@ const rootRouteChildren: RootRouteChildren = {
AboutRoute: AboutRoute,
ChangelogRoute: ChangelogRoute,
LoginRoute: LoginRoute,
RegisterRoute: RegisterRoute,
userPasswordChangeRoute: userPasswordChangeRoute,
OcpIndexRoute: OcpIndexRoute,
logisticsSiloAdjustmentsHistRoute: logisticsSiloAdjustmentsHistRoute,
logisticsDmIndexRoute: logisticsDmIndexRoute,
@@ -660,6 +704,8 @@ export const routeTree = rootRoute
"/about",
"/changelog",
"/login",
"/register",
"/(user)/passwordChange",
"/ocp/",
"/(logistics)/siloAdjustments/$hist",
"/(logistics)/dm/",
@@ -708,6 +754,12 @@ export const routeTree = rootRoute
"/login": {
"filePath": "login.tsx"
},
"/register": {
"filePath": "register.tsx"
},
"/(user)/passwordChange": {
"filePath": "(user)/passwordChange.tsx"
},
"/_admin/modules": {
"filePath": "_admin/modules.tsx",
"parent": "/_admin"

View File

@@ -0,0 +1,28 @@
import PasswordChange from "@/components/user/PasswordChange";
import { createFileRoute, redirect } from "@tanstack/react-router";
export const Route = createFileRoute("/(user)/passwordChange")({
component: RouteComponent,
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,
},
});
}
},
});
function RouteComponent() {
return (
<div>
<PasswordChange />
</div>
);
}

View File

@@ -80,8 +80,12 @@ export const Route = createRootRoute({
Hello {user?.username}
</DropdownMenuLabel>
<DropdownMenuSeparator />
{/* <DropdownMenuItem>Profile</DropdownMenuItem>
<DropdownMenuItem>Billing</DropdownMenuItem>
<DropdownMenuItem>
<Link to="/passwordChange">
Password Change
</Link>
</DropdownMenuItem>
{/* <DropdownMenuItem>Billing</DropdownMenuItem>
<DropdownMenuItem>Team</DropdownMenuItem>
<DropdownMenuItem>Subscription</DropdownMenuItem> */}
<hr className="solid"></hr>

View File

@@ -0,0 +1,106 @@
import RegisterForm from "@/components/auth/Register";
import { LstCard } from "@/components/extendedUI/LstCard";
import { CardContent, CardHeader } from "@/components/ui/card";
import { createFileRoute, redirect } from "@tanstack/react-router";
export const Route = createFileRoute("/register")({
component: RouteComponent,
beforeLoad: () => {
const isLoggedIn = localStorage.getItem("auth_token");
if (isLoggedIn) {
throw redirect({
to: "/",
});
}
},
});
function RouteComponent() {
return (
<div className="flex flex-row w-5/6 justify-between gap-2">
<RegisterForm />
<div>
<LstCard>
<CardHeader>
<h2 className="text-2xl font-bold mb-4">
Disclaimer and User Agreement
</h2>
</CardHeader>
<CardContent>
<div className="max-w-2xl mx-auto p-6 rounded-2xl shadow-md">
<ul className="list-decimal list-inside space-y-3">
<li>
<span className="font-bold">
Authentication Notice:
</span>
To interact with the Alpla prod through this
application, you must use your{" "}
<span className="font-semibold">
Windows login credentials
</span>
. These credentials are used solely for
authentication purposes.
</li>
{/* <li>
<span className="font-bold">
Password Privacy and Security:
</span>
This application{" "}
<span className="font-semibold">
does not store, sync, or transmit
</span>{" "}
your Windows password in any form.
Authentication is handled securely, and your
credentials are never logged or accessible
to the developers or third parties.
</li> */}
<li>
<span className="font-bold">
Data Handling:
</span>
All data accessed through the Alpla prod
remains the property of Alpla. The app
functions as a secure interface for
accessing and updating this information
where permitted by your role.
</li>
<li>
<span className="font-bold">
User Responsibility:
</span>
You are responsible for any actions
performed using your account. Please ensure
you do not share your credentials with
others and always log out of the application
when not in use.
</li>
<li>
<span className="font-bold">
No Warranty:
</span>
This application is provided "
<span className="italic">as is</span>"
without any warranty of any kind, either
expressed or implied. While every effort is
made to ensure functionality and data
accuracy, the app is subject to ongoing
development and may contain bugs or require
updates.
</li>
<li>
<span className="font-bold">
Consent Required:
</span>
By signing up or continuing to use this
application, you agree to the above terms
and acknowledge that you understand how your
login information is used.
</li>
</ul>
</div>
</CardContent>
</LstCard>
</div>
</div>
);
}

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "lstv2",
"version": "2.16.0",
"version": "2.16.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "lstv2",
"version": "2.16.0",
"version": "2.16.1",
"dependencies": {
"@dotenvx/dotenvx": "^1.39.0",
"@hono/node-server": "^1.14.0",

View File

@@ -1,95 +1,95 @@
{
"name": "lstv2",
"version": "2.16.0",
"type": "module",
"scripts": {
"dev": "concurrently -n \"server,frontend\" -c \"#007755,#2f6da3\" \"npm run dev:server\" \"cd frontend && npm run dev\"",
"dev:server": "dotenvx run -f .env -- tsx watch server/index.ts",
"dev:frontend": "cd frontend && npm run dev",
"dev:dbgen": " drizzle-kit generate --config=drizzle-dev.config.ts",
"dev:dbmigrate": " drizzle-kit migrate --config=drizzle-dev.config.ts",
"build": "npm run build:server && npm run build:frontend && npm run zipServer && npm run dev",
"build:server": "rimraf dist && tsc --build && npm run copy:scripts && xcopy server\\services\\notifications\\utils\\views\\ dist\\server\\services\\notifications\\utils\\views\\ /E /I /Y",
"build:frontend": "cd frontend && npm run build",
"copy:scripts": "tsx server/scripts/copyScripts.ts",
"copy:servers": "xcopy server\\services\\server\\utils\\serverData.json dist\\server\\services\\server\\utils /E /I /Y",
"start": "set NODE_ENV=production && npm run start:server",
"start:server": "dotenvx run -f .env -- node dist/server/index.js",
"db:generate": "npx drizzle-kit generate",
"db:migrate": "npx drizzle-kit push",
"db:dev": "npm run build && npm run db:generate && npm run db:migrate",
"deploy": "standard-version --conventional-commits && npm run prodBuild",
"zipServer": "dotenvx run -f .env -- tsx server/scripts/zipUpBuild.ts \"C:\\Users\\matthes01\\Documents\\lstv2\"",
"v1Build": "cd C:\\Users\\matthes01\\Documents\\logisticsSupportTool && npm run oldBuilder",
"scriptBuild": "powershell -ExecutionPolicy Bypass -File server/scripts/build.ps1 -dir \"C:\\Users\\matthes01\\Documents\\lstv2\"",
"removeOld": "rimraf dist && rimraf frontend/dist",
"prodBuild": "npm run v1Build && npm run build && npm run zipServer && npm run dev",
"commit": "cz",
"prodinstall": "npm i --omit=dev && npm run db:migrate",
"checkupdates": "npx npm-check-updates",
"testingCode": "dotenvx run -f .env -- tsx watch server/services/logistics/controller/warehouse/cycleCountChecks/cyclecountCheck.ts"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
"name": "lstv2",
"version": "2.16.1",
"type": "module",
"scripts": {
"dev": "concurrently -n \"server,frontend\" -c \"#007755,#2f6da3\" \"npm run dev:server\" \"cd frontend && npm run dev\"",
"dev:server": "dotenvx run -f .env -- tsx watch server/index.ts",
"dev:frontend": "cd frontend && npm run dev",
"dev:dbgen": " drizzle-kit generate --config=drizzle-dev.config.ts",
"dev:dbmigrate": " drizzle-kit migrate --config=drizzle-dev.config.ts",
"build": "npm run build:server && npm run build:frontend && npm run zipServer && npm run dev",
"build:server": "rimraf dist && tsc --build && npm run copy:scripts && xcopy server\\services\\notifications\\utils\\views\\ dist\\server\\services\\notifications\\utils\\views\\ /E /I /Y",
"build:frontend": "cd frontend && npm run build",
"copy:scripts": "tsx server/scripts/copyScripts.ts",
"copy:servers": "xcopy server\\services\\server\\utils\\serverData.json dist\\server\\services\\server\\utils /E /I /Y",
"start": "set NODE_ENV=production && npm run start:server",
"start:server": "dotenvx run -f .env -- node dist/server/index.js",
"db:generate": "npx drizzle-kit generate",
"db:migrate": "npx drizzle-kit push",
"db:dev": "npm run build && npm run db:generate && npm run db:migrate",
"deploy": "standard-version --conventional-commits && npm run build",
"zipServer": "dotenvx run -f .env -- tsx server/scripts/zipUpBuild.ts \"C:\\Users\\matthes01\\Documents\\lstv2\"",
"v1Build": "cd C:\\Users\\matthes01\\Documents\\logisticsSupportTool && npm run oldBuilder",
"scriptBuild": "powershell -ExecutionPolicy Bypass -File server/scripts/build.ps1 -dir \"C:\\Users\\matthes01\\Documents\\lstv2\"",
"removeOld": "rimraf dist && rimraf frontend/dist",
"prodBuild": "npm run v1Build && npm run build && npm run zipServer && npm run dev",
"commit": "cz",
"prodinstall": "npm i --omit=dev && npm run db:migrate",
"checkupdates": "npx npm-check-updates",
"testingCode": "dotenvx run -f .env -- tsx watch server/services/logistics/controller/warehouse/cycleCountChecks/cyclecountCheck.ts"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"admConfig": {
"build": 297,
"oldBuild": "backend-0.1.3.zip"
},
"devDependencies": {
"@types/adm-zip": "^0.5.7",
"@types/bcrypt": "^5.0.2",
"@types/fs-extra": "^11.0.4",
"@types/js-cookie": "^3.0.6",
"@types/mssql": "^9.1.7",
"@types/node": "^22.13.11",
"@types/node-cron": "^3.0.11",
"@types/nodemailer": "^6.4.17",
"@types/pg": "^8.11.11",
"@types/ws": "^8.18.0",
"concurrently": "^9.1.2",
"cz-conventional-changelog": "^3.3.0",
"standard-version": "^9.5.0",
"tsx": "^4.19.3",
"typescript": "^5.8.2"
},
"dependencies": {
"@dotenvx/dotenvx": "^1.39.0",
"@hono/node-server": "^1.14.0",
"@hono/zod-openapi": "^0.19.2",
"@scalar/hono-api-reference": "^0.7.2",
"@tanstack/react-form": "^1.2.1",
"@tanstack/react-table": "^8.21.2",
"@types/jsonwebtoken": "^9.0.9",
"@types/nodemailer-express-handlebars": "^4.0.5",
"adm-zip": "^0.5.16",
"axios": "^1.8.4",
"bcryptjs": "^3.0.2",
"croner": "^9.0.0",
"date-fns": "^4.1.0",
"date-fns-tz": "^3.2.0",
"drizzle-kit": "^0.30.5",
"drizzle-orm": "^0.41.0",
"drizzle-zod": "^0.7.0",
"excel-date-to-js": "^1.1.5",
"fast-xml-parser": "^5.0.9",
"fs-extra": "^11.3.0",
"jsonwebtoken": "^9.0.2",
"mssql": "^11.0.1",
"nodemailer": "^6.10.0",
"nodemailer-express-handlebars": "^7.0.0",
"pg": "^8.14.1",
"pino": "^9.6.0",
"pino-abstract-transport": "^2.0.0",
"pino-pretty": "^13.0.0",
"postgres": "^3.4.5",
"react-resizable-panels": "^2.1.7",
"rimraf": "^6.0.1",
"st-ethernet-ip": "^2.7.3",
"ws": "^8.18.1",
"xlsx": "^0.18.5",
"zod": "^3.24.2"
}
},
"admConfig": {
"build": 288,
"oldBuild": "backend-0.1.3.zip"
},
"devDependencies": {
"@types/adm-zip": "^0.5.7",
"@types/bcrypt": "^5.0.2",
"@types/fs-extra": "^11.0.4",
"@types/js-cookie": "^3.0.6",
"@types/mssql": "^9.1.7",
"@types/node": "^22.13.11",
"@types/node-cron": "^3.0.11",
"@types/nodemailer": "^6.4.17",
"@types/pg": "^8.11.11",
"@types/ws": "^8.18.0",
"concurrently": "^9.1.2",
"cz-conventional-changelog": "^3.3.0",
"standard-version": "^9.5.0",
"tsx": "^4.19.3",
"typescript": "^5.8.2"
},
"dependencies": {
"@dotenvx/dotenvx": "^1.39.0",
"@hono/node-server": "^1.14.0",
"@hono/zod-openapi": "^0.19.2",
"@scalar/hono-api-reference": "^0.7.2",
"@tanstack/react-form": "^1.2.1",
"@tanstack/react-table": "^8.21.2",
"@types/jsonwebtoken": "^9.0.9",
"@types/nodemailer-express-handlebars": "^4.0.5",
"adm-zip": "^0.5.16",
"axios": "^1.8.4",
"bcryptjs": "^3.0.2",
"croner": "^9.0.0",
"date-fns": "^4.1.0",
"date-fns-tz": "^3.2.0",
"drizzle-kit": "^0.30.5",
"drizzle-orm": "^0.41.0",
"drizzle-zod": "^0.7.0",
"excel-date-to-js": "^1.1.5",
"fast-xml-parser": "^5.0.9",
"fs-extra": "^11.3.0",
"jsonwebtoken": "^9.0.2",
"mssql": "^11.0.1",
"nodemailer": "^6.10.0",
"nodemailer-express-handlebars": "^7.0.0",
"pg": "^8.14.1",
"pino": "^9.6.0",
"pino-abstract-transport": "^2.0.0",
"pino-pretty": "^13.0.0",
"postgres": "^3.4.5",
"react-resizable-panels": "^2.1.7",
"rimraf": "^6.0.1",
"st-ethernet-ip": "^2.7.3",
"ws": "^8.18.1",
"xlsx": "^0.18.5",
"zod": "^3.24.2"
}
}
}

View File

@@ -146,7 +146,7 @@ $plantFunness = {
############################################################################
Write-Host "Stopping the services to do the updates, pkgs and db changes."
Write-Host "Stopping services to update"
#Write-Host "Stopping services to update"
$serviceGateway = "LST-Gateway$(if ($token -eq "usiow2") { "_2" })"
$serviceAuth = "LST-Auth$(if ($token -eq "usiow2") { "_2" })"
$serviceSystem = "LST-System$(if ($token -eq "usiow2") { "_2" })"
@@ -155,30 +155,30 @@ $plantFunness = {
$serviceOcme = "LST-Ocme$(if ($token -eq "usiow2") { "_2" })"
$serviceLstV2 = "LSTV2$(if ($token -eq "usiow2") { "_2" })"
if($token -eq "usday1"){
Write-Host "Stopping $($serviceOcme)"
Stop-Service -DisplayName $serviceOcme -Force
}
# if($token -eq "usday1"){
# Write-Host "Stopping $($serviceOcme)"
# Stop-Service -DisplayName $serviceOcme -Force
# }
Write-Host "Stopping $($serviceGateway)"
Stop-Service -DisplayName $serviceGateway -Force
Start-Sleep -Seconds 1
# Write-Host "Stopping $($serviceGateway)"
# Stop-Service -DisplayName $serviceGateway -Force
# Start-Sleep -Seconds 1
Write-Host "Stopping $($serviceAuth)"
Stop-Service -DisplayName $serviceAuth -Force
Start-Sleep -Seconds 1
# Write-Host "Stopping $($serviceAuth)"
# Stop-Service -DisplayName $serviceAuth -Force
# Start-Sleep -Seconds 1
Write-Host "Stopping $($serviceSystem)"
Stop-Service -DisplayName $serviceSystem -Force
Start-Sleep -Seconds 1
# Write-Host "Stopping $($serviceSystem)"
# Stop-Service -DisplayName $serviceSystem -Force
# Start-Sleep -Seconds 1
Write-Host "Stopping $($serviceApp)"
Stop-Service -DisplayName $serviceApp -Force
Start-Sleep -Seconds 1
# Write-Host "Stopping $($serviceApp)"
# Stop-Service -DisplayName $serviceApp -Force
# Start-Sleep -Seconds 1
Write-Host "Stopping $($serviceFrontEnd)"
Stop-Service -DisplayName $serviceFrontEnd -Force
Start-Sleep -Seconds 1
# Write-Host "Stopping $($serviceFrontEnd)"
# Stop-Service -DisplayName $serviceFrontEnd -Force
# Start-Sleep -Seconds 1
Write-Host "Stopping $($serviceLstV2)"
Stop-Service -DisplayName $serviceLstV2 -Force
@@ -194,11 +194,16 @@ $plantFunness = {
Write-Host "Removing services that are no longer used."
& $nssmPath remove "LogisticsSupportTool" confirm
& $nssmPath remove $serviceAuth confirm
& $nssmPath remove $serviceGateway confirm
& $nssmPath remove $serviceSystem confirm
& $nssmPath remove $serviceApp confirm
& $nssmPath remove $serviceFrontEnd confirm
& $nssmPath remove $serviceOcme confirm
# & $nssmPath remove $serviceGateway confirm
# if($token -eq "usday1"){
# & $nssmPath remove $serviceOcme confirm
# }
Start-Sleep -Seconds 5
Start-Sleep -Seconds 2
$service = Get-Service -Name $serviceLstV2 -ErrorAction SilentlyContinue
@@ -233,188 +238,188 @@ $plantFunness = {
# Frontend env
###########################################################
Write-Host "Creating the env file in the front end"
$envContentTemplatef = @"
NEXTAUTH_SECRET= "12348fssad5sdg2f2354afvfw34"
NEXTAUTH_URL_INTERNAL= "http://localhost:3000"
NEXTAUTH_URL="{url}"
API_KEY= "E3ECD3619A943B98C6F33E3322362"
"@
# Write-Host "Creating the env file in the front end"
# $envContentTemplatef = @"
# NEXTAUTH_SECRET= "12348fssad5sdg2f2354afvfw34"
# NEXTAUTH_URL_INTERNAL= "http://localhost:3000"
# NEXTAUTH_URL="{url}"
# API_KEY= "E3ECD3619A943B98C6F33E3322362"
# "@
try {
$url = "http://$($token)vms006:3000"
# try {
# $url = "http://$($token)vms006:3000"
if ($token -eq "usiow2") {
$url = "http://usiow1vms006:3001"
}
# if ($token -eq "usiow2") {
# $url = "http://usiow1vms006:3001"
# }
if ($token -in @("test1", "test2", "test3")) {
$url = "http://usmcd1vms036:3000"
}
# if ($token -in @("test1", "test2", "test3")) {
# $url = "http://usmcd1vms036:3000"
# }
# Replace {url} with the actual $url
$envContentf = $envContentTemplatef -replace "{url}", $url
# # Replace {url} with the actual $url
# $envContentf = $envContentTemplatef -replace "{url}", $url
# Define the path where the .env file should be created
$envFilePathf = $obslst + "\apps\frontend\.env"
Write-Host "Final URL: $url"
# Write the content to the .env file
$envContentf | Out-File -FilePath $envFilePathf -Encoding UTF8 -Force
# # Define the path where the .env file should be created
# $envFilePathf = $obslst + "\apps\frontend\.env"
# Write-Host "Final URL: $url"
# # Write the content to the .env file
# $envContentf | Out-File -FilePath $envFilePathf -Encoding UTF8 -Force
# Optional: Verify the file was created
if (Test-Path $envFilePathf) {
Write-Host "`.env` file created successfully on $env:COMPUTERNAME at $envFilePathf"
} else {
Write-Host "Failed to create `.env` file on $env:COMPUTERNAME"
}
# # Optional: Verify the file was created
# if (Test-Path $envFilePathf) {
# Write-Host "`.env` file created successfully on $env:COMPUTERNAME at $envFilePathf"
# } else {
# Write-Host "Failed to create `.env` file on $env:COMPUTERNAME"
# }
} catch {
Write-Host "Error: Failed to create `.env` file on $server - $_"
}
# } catch {
# Write-Host "Error: Failed to create `.env` file on $server - $_"
# }
###########################################################
# DB env
###########################################################
Write-Host "Creating the env file in the front end"
$envContentTemplateb = @"
DATABASE_URL="file:E:\LST\db\{dbLink}.db"
"@
# Write-Host "Creating the env file in the front end"
# $envContentTemplateb = @"
# DATABASE_URL="file:E:\LST\db\{dbLink}.db"
# "@
try {
# try {
$dbLink = "lstBackendDB"
# $dbLink = "lstBackendDB"
if ($token -eq "usiow2") {
$dbLink = "lstBackendDB_2"
}
# if ($token -eq "usiow2") {
# $dbLink = "lstBackendDB_2"
# }
if ($token -in @("test1", "test2", "test3")) {
$dbLink = "lstBackendDB"
}
# if ($token -in @("test1", "test2", "test3")) {
# $dbLink = "lstBackendDB"
# }
# Replace {url} with the actual $url
$envContentb = $envContentTemplateb -replace "{dbLink}", $dbLink
# # Replace {url} with the actual $url
# $envContentb = $envContentTemplateb -replace "{dbLink}", $dbLink
# Define the path where the .env file should be created
$envFilePathb = $obslst + "\packages\database\.env"
# # Define the path where the .env file should be created
# $envFilePathb = $obslst + "\packages\database\.env"
# Write the content to the .env file
$envContentb | Out-File -FilePath $envFilePathb -Encoding UTF8 -Force
# # Write the content to the .env file
# $envContentb | Out-File -FilePath $envFilePathb -Encoding UTF8 -Force
# Optional: Verify the file was created
if (Test-Path $envFilePathb) {
Write-Host "`.env` file created successfully on $env:COMPUTERNAME at $envFilePathb"
} else {
Write-Host "Failed to create `.env` file on $env:COMPUTERNAME"
}
# # Optional: Verify the file was created
# if (Test-Path $envFilePathb) {
# Write-Host "`.env` file created successfully on $env:COMPUTERNAME at $envFilePathb"
# } else {
# Write-Host "Failed to create `.env` file on $env:COMPUTERNAME"
# }
} catch {
Write-Host "Error: Failed to create `.env` file on $server - $_"
}
# } catch {
# Write-Host "Error: Failed to create `.env` file on $server - $_"
# }
###########################################################
# backend env
###########################################################
Write-Host "Creating the env file in the front end"
$envContentTemplated = @"
# Server env
NODE_ENV = production
# server apiKey
API_KEY = E3ECD3619A943B98C6F33E3322362
# Prisma DB link
DATABASE_URL="file:E:\LST\db\{dbLink}.db"
# if you still want the db in the same folder as the server install you need to do like the example below else use the relevent link
DATEBASE_LOC="E:\LST\db\{dbLink}.db"
DATABASE_BACKUP_LOC="E:\LST\backups"
# Server port
GATEWAY_PORT={gatewayport}
AUTH_PORT=4100
SYSTEM_APP_PORT={systemport}
OCME_PORT={ocme}
# Write-Host "Creating the env file in the front end"
# $envContentTemplated = @"
# # Server env
# NODE_ENV = production
# # server apiKey
# API_KEY = E3ECD3619A943B98C6F33E3322362
# # Prisma DB link
# DATABASE_URL="file:E:\LST\db\{dbLink}.db"
# # if you still want the db in the same folder as the server install you need to do like the example below else use the relevent link
# DATEBASE_LOC="E:\LST\db\{dbLink}.db"
# DATABASE_BACKUP_LOC="E:\LST\backups"
# # Server port
# GATEWAY_PORT={gatewayport}
# AUTH_PORT=4100
# SYSTEM_APP_PORT={systemport}
# OCME_PORT={ocme}
# This should me removed once we have the entire app broke out to its own apps
OLD_APP_PORT={appPort}
# # This should me removed once we have the entire app broke out to its own apps
# OLD_APP_PORT={appPort}
# Logging
LOG_LEVEL = info
LOG_LOC ="E:\\LST\\logs"
# # Logging
# LOG_LEVEL = info
# LOG_LOC ="E:\\LST\\logs"
# authentication
SALTING = 12
SECRET = E3ECD3619A943B98C6F33E3322362
JWT_SECRET = 12348fssad5sdg2f2354afvfw34
JWT_EXPIRES_TIME = 1h
# # authentication
# SALTING = 12
# SECRET = E3ECD3619A943B98C6F33E3322362
# JWT_SECRET = 12348fssad5sdg2f2354afvfw34
# JWT_EXPIRES_TIME = 1h
# cookie time is in min please take this into consideration when creating all the times
COOKIE_EXPIRES_TIME = 60
# # cookie time is in min please take this into consideration when creating all the times
# COOKIE_EXPIRES_TIME = 60
# password token reset in mintues
RESET_TOKEN = 330
"@
# # password token reset in mintues
# RESET_TOKEN = 330
# "@
try {
# try {
$dbLink = "lstBackendDB"
$gatewayport = "4400"
$systemport = "4200"
$ocmeport = "4300"
$appport = "4400"
# $dbLink = "lstBackendDB"
# $gatewayport = "4400"
# $systemport = "4200"
# $ocmeport = "4300"
# $appport = "4400"
if ($token -eq "usiow2") {
$dbLink = "lstBackendDB_2"
$gatewayport = "4401"
$systemport = "4201"
$ocmeport = "4301"
$appport = "4401"
}
# if ($token -eq "usiow2") {
# $dbLink = "lstBackendDB_2"
# $gatewayport = "4401"
# $systemport = "4201"
# $ocmeport = "4301"
# $appport = "4401"
# }
if ($token -in @("test1", "test2", "test3")) {
$dbLink = "lstBackendDB"
}
# if ($token -in @("test1", "test2", "test3")) {
# $dbLink = "lstBackendDB"
# }
#
$port1 = $envContentTemplated -replace "{gatewayport}", $gatewayport
$port2 = $port1 -replace "{systemport}", $systemport
$port3 = $port2 -replace "{ocme}", $ocmeport
$port4 = $port3 -replace "{appPort}", $appport
$envContentd = $port4 -replace "{dbLink}", $dbLink
# #
# $port1 = $envContentTemplated -replace "{gatewayport}", $gatewayport
# $port2 = $port1 -replace "{systemport}", $systemport
# $port3 = $port2 -replace "{ocme}", $ocmeport
# $port4 = $port3 -replace "{appPort}", $appport
# $envContentd = $port4 -replace "{dbLink}", $dbLink
# Define the path where the .env file should be created
$envFilePathd = $obslst + "\.env"
# # Define the path where the .env file should be created
# $envFilePathd = $obslst + "\.env"
# Write the content to the .env file
$envContentd | Out-File -FilePath $envFilePathd -Encoding UTF8 -Force
# # Write the content to the .env file
# $envContentd | Out-File -FilePath $envFilePathd -Encoding UTF8 -Force
# Optional: Verify the file was created
if (Test-Path $envFilePathd) {
Write-Host "`.env` file created successfully on $env:COMPUTERNAME at $envFilePathd"
} else {
Write-Host "Failed to create `.env` file on $env:COMPUTERNAME"
}
# # Optional: Verify the file was created
# if (Test-Path $envFilePathd) {
# Write-Host "`.env` file created successfully on $env:COMPUTERNAME at $envFilePathd"
# } else {
# Write-Host "Failed to create `.env` file on $env:COMPUTERNAME"
# }
} catch {
Write-Host "Error: Failed to create `.env` file on $server - $_"
}
# } catch {
# Write-Host "Error: Failed to create `.env` file on $server - $_"
# }
Write-Host "Running install on obs server."
Set-Location $obslst
npm run newinstall # --omit=dev
Write-Host "Update the frontend"
npm run install:front
npm run install:ui
npm run install:db
# Write-Host "Running install on obs server."
# Set-Location $obslst
# npm run newinstall # --omit=dev
# Write-Host "Update the frontend"
# npm run install:front
# npm run install:ui
# npm run install:db
Write-Host "Running db updates"
npm run db:migrate
Start-Sleep -Seconds 1
npm run db:gen
Start-Sleep -Seconds 1
Write-Host "incase a new default setting was added we want to add it in."
npm run db:init
# Write-Host "Running db updates"
# npm run db:migrate
# Start-Sleep -Seconds 1
# npm run db:gen
# Start-Sleep -Seconds 1
# Write-Host "incase a new default setting was added we want to add it in."
# npm run db:init
###########################################################
# Starting the services back up.

View File

@@ -51,7 +51,7 @@ export const registerUser = async (
setSysAdmin(updateUser, "systemAdmin");
}
return { sucess: true, message: "User Registered", user };
return { success: true, message: "User Registered", user };
} catch (error) {
createLog("error", "auth", "auth", `${error}`);
return {

View File

@@ -6,6 +6,7 @@ import type { User } from "../../../../types/users.js";
import { createPassword } from "../../utils/createPassword.js";
import { createLog } from "../../../logger/logger.js";
import { sendEmail } from "../../../notifications/controller/sendMail.js";
import { settings } from "../../../../../database/schema/settings.js";
export const updateUserADM = async (userData: User) => {
/**
@@ -13,7 +14,6 @@ export const updateUserADM = async (userData: User) => {
* password, username, email.
*/
console.log(userData);
createLog(
"info",
"apiAuthedRoute",
@@ -39,6 +39,21 @@ export const updateUserADM = async (userData: User) => {
"The user you are looking for has either been deleted or dose not exist.",
};
}
const { data: s, error: se } = await tryCatch(db.select().from(settings));
if (se) {
return {
success: false,
message: `There was an error getting setting data to post to the server.`,
data: se,
};
}
const set: any = s;
const server = set.filter((n: any) => n.name === "server");
const port = set.filter((n: any) => n.name === "serverPort");
const upd_user = user as User;
const password: string = userData.password
? await createPassword(userData.password!)
@@ -72,6 +87,8 @@ export const updateUserADM = async (userData: User) => {
context: {
password: userData.password!,
username: user[0].username!,
server: server[0].value,
port: port[0].value,
},
});
}

View File

@@ -7,114 +7,124 @@ import { createLog } from "../../../logger/logger.js";
const app = new OpenAPIHono();
const UserSchema = z.object({
password: z
.string()
.min(6, { message: "Passwords must be longer than 3 characters" })
.regex(/[A-Z]/, {
message: "Password must contain at least one uppercase letter",
})
.regex(/[\W_]/, {
message: "Password must contain at least one special character",
})
.openapi({ example: "Password1!" }),
password: z
.string()
.min(6, { message: "Passwords must be longer than 3 characters" })
.regex(/[A-Z]/, {
message: "Password must contain at least one uppercase letter",
})
.regex(/[\W_]/, {
message: "Password must contain at least one special character",
})
.openapi({ example: "Password1!" }),
});
app.openapi(
createRoute({
tags: ["auth:user"],
summary: "Updates a users Profile",
description: "Currently you can only update your password over the API",
method: "post",
path: "/profile",
middleware: authMiddleware,
request: {
body: {
content: {
"application/json": { schema: UserSchema },
createRoute({
tags: ["auth:user"],
summary: "Updates a users Profile",
description: "Currently you can only update your password over the API",
method: "patch",
path: "/profile",
middleware: authMiddleware,
request: {
body: {
content: {
"application/json": { schema: UserSchema },
},
},
},
},
},
responses: {
200: {
content: {
"application/json": {
schema: z.object({
message: z
.string()
.optional()
.openapi({ example: "User Profile has been updated" }),
}),
},
responses: {
200: {
content: {
"application/json": {
schema: z.object({
message: z.string().optional().openapi({
example: "User Profile has been updated",
}),
}),
},
},
description: "Sucess return",
},
401: {
content: {
"application/json": {
schema: z.object({
message: z
.string()
.optional()
.openapi({ example: "Unauthenticated" }),
}),
},
},
description: "Unauthorized",
},
500: {
content: {
"application/json": {
schema: z.object({
message: z
.string()
.optional()
.openapi({ example: "Internal Server error" }),
}),
},
},
description: "Internal Server Error",
},
},
description: "Sucess return",
},
401: {
content: {
"application/json": {
schema: z.object({
message: z
.string()
.optional()
.openapi({ example: "Unauthenticated" }),
}),
},
},
description: "Unauthorized",
},
500: {
content: {
"application/json": {
schema: z.object({
message: z
.string()
.optional()
.openapi({ example: "Internal Server error" }),
}),
},
},
description: "Internal Server Error",
},
},
}),
async (c) => {
// make sure we have a vaid user being accessed thats really logged in
const authHeader = c.req.header("Authorization");
}),
async (c) => {
// make sure we have a vaid user being accessed thats really logged in
const authHeader = c.req.header("Authorization");
if (authHeader?.includes("Basic")) {
return c.json(
{ message: "You are a Basic user! Please login to get a token" },
401
);
if (authHeader?.includes("Basic")) {
return c.json(
{
message:
"You are a Basic user! Please login to get a token",
},
401
);
}
if (!authHeader) {
return c.json({ success: false, message: "Unauthorized" }, 401);
}
const token = authHeader?.split("Bearer ")[1] || "";
let user;
try {
const payload = await verify(token, process.env.JWT_SECRET!);
user = payload.user;
} catch (error) {
createLog(
"error",
"lst",
"auth",
"Failed session check, user must be logged out"
);
return c.json({ success: false, message: "Unauthorized" }, 401);
}
// now pass all the data over to update the user info
try {
const data = await c?.req.json();
await updateProfile(user, data, token);
return c.json({
success: true,
message: "Your profile has been updated",
});
} catch (error) {
console.log(error);
return c.json({
success: false,
message: "There was an error",
error,
});
}
}
if (!authHeader) {
return c.json({ message: "Unauthorized" }, 401);
}
const token = authHeader?.split("Bearer ")[1] || "";
let user;
try {
const payload = await verify(token, process.env.JWT_SECRET!);
user = payload.user;
} catch (error) {
createLog(
"error",
"lst",
"auth",
"Failed session check, user must be logged out"
);
return c.json({ message: "Unauthorized" }, 401);
}
// now pass all the data over to update the user info
try {
const data = await c?.req.json();
await updateProfile(user, data, token);
return c.json({ message: "Your profile has been updated" });
} catch (error) {
return c.json({ message: "There was an error", error });
}
}
);
export default app;

View File

@@ -1,3 +1,4 @@
import { lorealForecast } from "./mappings/loralForecast.js";
import { standardForecast } from "./mappings/standardForcast.js";
export const forecastIn = async (data: any, user: any) => {
@@ -12,7 +13,7 @@ export const forecastIn = async (data: any, user: any) => {
// what type of order are we dealing with?
if (data["fileType"] === "standard") {
//run the standard forecast in
const standard = await standardForecast(data["postPostForecast"], user);
const standard = await standardForecast(data["postForecast"], user);
success = standard.success ?? false;
message = standard.message ?? "Error posting standard forecast";
orderData = standard.data;
@@ -23,7 +24,11 @@ export const forecastIn = async (data: any, user: any) => {
}
if (data["fileType"] === "loreal") {
// orders in
//run the standard forecast in
const loreal = await lorealForecast(data["postForecast"], user);
success = loreal.success ?? false;
message = loreal.message ?? "Error posting standard forecast";
orderData = loreal.data;
}
if (data["fileType"] === "pg") {

View File

@@ -0,0 +1,110 @@
import { db } from "../../../../../../../database/dbclient.js";
import { settings } from "../../../../../../../database/schema/settings.js";
import { tryCatch } from "../../../../../../globalUtils/tryCatch.js";
import XLSX from "xlsx";
import { excelDateStuff } from "../../../../utils/excelDateStuff.js";
import { postForecast } from "../postForecast.js";
let customerID = 4;
export const lorealForecast = async (data: any, user: any) => {
/**
* Post a standard forecast based on the standard template.
*/
const { data: s, error: e } = await tryCatch(db.select().from(settings));
if (e) {
return {
sucess: false,
message: `Error getting settings`,
data: e,
};
}
const plantToken = s.filter((s) => s.name === "plantToken");
const arrayBuffer = await data.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
const workbook = XLSX.read(buffer, { type: "buffer" });
const sheet: any = workbook.Sheets["Alpla HDPE"];
const range = XLSX.utils.decode_range(sheet["!ref"]);
const headers = [];
for (let C = range.s.c; C <= range.e.c; ++C) {
const cellAddress = XLSX.utils.encode_cell({ r: 0, c: C }); // row 0 = Excel row 1
const cell = sheet[cellAddress];
headers.push(cell ? cell.v : undefined);
}
const forecastData: any = XLSX.utils.sheet_to_json(sheet, {
defval: "",
header: headers,
range: 2,
});
// Extract the customer code (assuming it's always present)
const customerCode = forecastData["NORTH HDPE BOTTLES"];
// Filter out non-date properties (these are your metadata fields)
const metadataFields = [
"NORTH HDPE BOTTLES",
"BLOCKED",
"INVENTORY",
"CALL-OFF",
"PALLET CONSUMPTION",
];
const foreCastData: any = [];
// process the forcast
forecastData.forEach((item: any, index: any) => {
//console.log(`Processing item ${index + 1} of ${forecastData.length}`);
// Extract the customer code
const customerCode = item["NORTH HDPE BOTTLES"];
// Process each date in the current object
for (const [date, qty] of Object.entries(item)) {
// Skip metadata fields
if (metadataFields.includes(date)) continue;
if (qty === 0) continue;
// Create your transformed record
const record = {
customerArticleNo: customerCode,
requirementDate: excelDateStuff(parseInt(date)),
quantity: qty,
};
// Do something with this record
foreCastData.push(record);
}
});
const predefinedObject = {
receivingPlantId: plantToken[0].value,
documentName: `ForecastFromLST-${new Date(Date.now()).toLocaleString(
"en-US"
)}`,
sender: user.username || "lst-system",
customerId: customerID,
positions: [],
};
let updatedPredefinedObject = {
...predefinedObject,
positions: [
...predefinedObject.positions,
...foreCastData.filter((q: any) => q.customerArticleNo != ""),
],
};
const posting: any = await postForecast(updatedPredefinedObject, user);
return {
success: posting.success,
message: posting.message,
data: posting.data,
};
};

View File

@@ -1,11 +1,114 @@
import { db } from "../../../../../../../database/dbclient.js";
import { settings } from "../../../../../../../database/schema/settings.js";
import { tryCatch } from "../../../../../../globalUtils/tryCatch.js";
import XLSX from "xlsx";
import { excelDateStuff } from "../../../../utils/excelDateStuff.js";
import { postForecast } from "../postForecast.js";
export const standardForecast = async (data: any, user: any) => {
/**
* Post a standard forecast based on the standard template.
*/
const { data: s, error: e } = await tryCatch(db.select().from(settings));
if (e) {
return {
sucess: false,
message: `Error getting settings`,
data: e,
};
}
const plantToken = s.filter((s) => s.name === "plantToken");
const arrayBuffer = await data.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
const workbook = XLSX.read(buffer, { type: "buffer" });
const sheetName = workbook.SheetNames[0];
const sheet = workbook.Sheets[sheetName];
const headers = [
"CustomerArticleNumber",
"Quantity",
"RequirementDate",
"CustomerID",
];
const forecastData: any = XLSX.utils.sheet_to_json(sheet, {
defval: "",
header: headers,
range: 1,
});
const groupedByCustomer: any = forecastData.reduce(
(acc: any, item: any) => {
const id = item.CustomerID;
if (!acc[id]) {
acc[id] = [];
}
acc[id].push(item);
return acc;
},
{}
);
const foreCastData: any = [];
for (const [customerID, forecast] of Object.entries(groupedByCustomer)) {
//console.log(`Running for Customer ID: ${customerID}`);
const newForecast: any = forecast;
const predefinedObject = {
receivingPlantId: plantToken[0].value,
documentName: `ForecastFromLST-${new Date(
Date.now()
).toLocaleString("en-US")}`,
sender: user.username || "lst-system",
customerId: customerID,
positions: [],
};
// map everything out for each order
const nForecast = newForecast.map((o: any) => {
// const invoice = i.filter(
// (i: any) => i.deliveryAddress === parseInt(customerID)
// );
// if (!invoice) {
// return;
// }
return {
customerArticleNo: o.CustomerArticleNumber,
requirementDate: excelDateStuff(parseInt(o.RequirementDate)),
quantity: o.Quantity,
};
});
// do that fun combining thing
let updatedPredefinedObject = {
...predefinedObject,
positions: [...predefinedObject.positions, ...nForecast],
};
//console.log(updatedPredefinedObject);
// post the orders to the server
const posting: any = await postForecast(updatedPredefinedObject, user);
foreCastData.push({
customer: customerID,
//totalOrders: orders?.length(),
success: posting.success,
message: posting.message,
data: posting.data,
});
}
return {
success: true,
message: "Forecast Posted",
data: [],
data: foreCastData,
};
};

View File

@@ -0,0 +1,167 @@
import XLSX from "xlsx";
import { tryCatch } from "../../../../../../globalUtils/tryCatch.js";
import { db } from "../../../../../../../database/dbclient.js";
import { settings } from "../../../../../../../database/schema/settings.js";
import { query } from "../../../../../sqlServer/prodSqlServer.js";
import { orderState } from "../../../../../sqlServer/querys/dm/orderState.js";
import { excelDateStuff } from "../../../../utils/excelDateStuff.js";
import { invoiceAddress } from "../../../../../sqlServer/querys/dm/invoiceAddress.js";
import { postOrders } from "../postOrders.js";
export const energizerOrders = async (data: any, user: any) => {
/**
* Standard orders meaning that we get the standard file exported and fill it out and uplaod to lst.
*/
const { data: s, error: e } = await tryCatch(db.select().from(settings));
if (e) {
return {
sucess: false,
message: `Error getting settings`,
data: e,
};
}
// order state
const { data: openOrders, error: oe } = await tryCatch(
query(orderState, "Gets the next 500 orders that have not been started")
);
if (oe) {
return {
sucess: false,
message: `Error getting article data`,
data: oe,
};
}
// order state
const { data: i, error: ie } = await tryCatch(
query(invoiceAddress, "Gets invoices addresses")
);
if (ie) {
return {
sucess: false,
message: `Error getting invoice address data`,
data: ie,
};
}
const plantToken = s.filter((s) => s.name === "plantToken");
const arrayBuffer = await data.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
const workbook = XLSX.read(buffer, { type: "buffer" });
const sheetName = workbook.SheetNames[0];
const sheet = workbook.Sheets[sheetName];
// define custom headers
const headers = [
"ITEM",
"PO",
"ReleaseNo",
"QTY",
"DELDATE",
"COMMENTS",
"What changed",
"CUSTOMERID",
];
const orderData = XLSX.utils.sheet_to_json(sheet, {
defval: "",
header: headers,
range: 1,
});
// the base of the import
const predefinedObject = {
receivingPlantId: plantToken[0].value,
documentName: `OrdersFromLST-${new Date(Date.now()).toLocaleString(
"en-US"
)}`,
sender: user.username || "lst-system",
externalRefNo: `OrdersFromLST-${new Date(Date.now()).toLocaleString(
"en-US"
)}`,
orders: [],
};
let newOrders: any = orderData;
// filter out the orders that have already been started just to reduce the risk of errors.
newOrders.filter((oo: any) =>
openOrders.some(
(o: any) => o.CustomerOrderNumber === oo.CustomerOrderNumber
)
);
// filter out the blanks
newOrders = newOrders.filter((z: any) => z.ITEM !== "");
// let postedOrders: any = [];
// for (const [customerID, orders] of Object.entries(orderData)) {
// // console.log(`Running for Customer ID: ${customerID}`);
// const newOrders: any = orderData;
// // filter out the orders that have already been started just to reduce the risk of errors.
// newOrders.filter((oo: any) =>
// openOrders.some(
// (o: any) => o.CustomerOrderNumber === oo.CustomerOrderNumber
// )
// );
// // map everything out for each order
const nOrder = newOrders.map((o: any) => {
const invoice = i.filter(
(i: any) => i.deliveryAddress === parseInt(o.CUSTOMERID)
);
if (!invoice) {
return;
}
return {
customerId: parseInt(o.CUSTOMERID),
invoiceAddressId: invoice[0].invoiceAddress, // matched to the default invoice address
customerOrderNo: o.PO,
orderDate: new Date(Date.now()).toLocaleString("en-US"),
positions: [
{
deliveryAddressId: parseInt(o.CUSTOMERID),
customerArticleNo: o.ITEM,
quantity: parseInt(o.QTY),
deliveryDate: o.DELDATE, //excelDateStuff(o.DELDATE),
customerLineItemNo: o.ReleaseNo, // this is how it is currently sent over from abbott
customerReleaseNo: o.ReleaseNo, // same as above
},
],
};
});
// // do that fun combining thing
const updatedPredefinedObject = {
...predefinedObject,
orders: [...predefinedObject.orders, ...nOrder],
};
// //console.log(updatedPredefinedObject);
// // post the orders to the server
const posting: any = await postOrders(updatedPredefinedObject, user);
return {
customer: nOrder[0].CUSTOMERID,
//totalOrders: orders?.length(),
success: posting.success,
message: posting.message,
data: posting.data,
};
// }
// return {
// success: true,
// message:
// "Standard Template was just processed successfully, please check AlplaProd 2.0 to confirm no errors. ",
// data: nOrder,
// };
};

View File

@@ -100,7 +100,27 @@ export const standardOrders = async (data: any, user: any) => {
let postedOrders: any = [];
for (const [customerID, orders] of Object.entries(groupedByCustomer)) {
// console.log(`Running for Customer ID: ${customerID}`);
const newOrders: any = orders;
const filterOrders: any = orders;
const newOrders: any = [];
//newOrders.filter((oo) => openOrders.some((o) => String(o.CustomerOrderNumber) === String(oo.CustomerOrderNumber)));
//console.log(newOrders)
filterOrders.forEach((oo: any) => {
const isMatch = openOrders.some(
(o: any) =>
String(o.CustomerOrderNumber).trim() ===
String(oo.CustomerOrderNumber).trim()
);
if (!isMatch) {
console.log(`ok to update: ${oo.CustomerOrderNumber}`);
newOrders.push(oo);
} else {
console.log(
`Not valid order to update: ${oo.CustomerOrderNumber}`
);
//console.log(oo)
}
});
// filter out the orders that have already been started just to reduce the risk of errors.
newOrders.filter((oo: any) =>
@@ -119,7 +139,7 @@ export const standardOrders = async (data: any, user: any) => {
}
return {
customerId: parseInt(customerID),
invoiceAddressId: invoice[0].invoiceAddress, // matched to the default invoice address
invoiceAddressId: invoice[0]?.invoiceAddress, // matched to the default invoice address
customerOrderNo: o.CustomerOrderNumber,
orderDate: new Date(Date.now()).toLocaleString("en-US"),
positions: [

View File

@@ -1,4 +1,5 @@
import { abbottOrders } from "./mappings/abbottTruckList.js";
import { energizerOrders } from "./mappings/energizerOrdersIn.js";
import { standardOrders } from "./mappings/standardOrders.js";
export const ordersIn = async (data: any, user: any) => {
@@ -29,6 +30,10 @@ export const ordersIn = async (data: any, user: any) => {
if (data["fileType"] === "energizer") {
// orders in
const energizer = await energizerOrders(data["postOrders"], user);
success = energizer.success ?? false;
message = energizer.message ?? "Error posting Energizer Orders";
orderData = energizer.data;
}
if (data["fileType"] === "loreal") {

View File

@@ -53,10 +53,11 @@ app.openapi(
);
if (error) {
console.log(error);
return c.json(
{
success: false,
message: "Error posting forecast",
message: "Error posting Orders",
data: error,
},
400

View File

@@ -54,6 +54,7 @@ app.openapi(
);
if (error) {
console.log(error);
return c.json(
{
success: false,

View File

@@ -22,11 +22,17 @@
Your password was change. Please find your new temporary password below:<br/><br/>
Temporary Password: <em><b>{{password}}</b></em><br/><br/>
Temporary Password: <em><b>{{password}}</b></em><br/><br/>
For security reasons, we strongly recommend changing your password as soon as possible.<br/><br/>
You can update it by logging into your account and navigating to the password settings section.<br/><br/>
You can update it by logging into your account, clicking your profile at the top right and click password change.<br/><br/>
Or <a href="http://{{server}}:{{port}}/passwordChange"
style="display:inline-block; padding:10px 20px; text-decoration:none; border-radius:5px;">
Click Here
</a> to login and change your password.<br/><br/>
Best regards,<br/><br/>
LST team<br/>

View File

@@ -47,7 +47,7 @@ export const assignedPrinters = async () => {
//console.log(lots);
return;
//return;
for (let i = 0; i < printers.length; i++) {
// is the printer assinged in alplalabel online?
const assigned = lots?.filter(

View File

@@ -1,8 +1,25 @@
export const orderState = `
SELECT top(500)
SELECT top(10000)
CustomerOrderNumber
, OrderState
, r.ReleaseState
, h.CreatedByEdi
--, *
FROM [test1_AlplaPROD2.0_Read].[order].[Header] (nolock)
where OrderState = 0
FROM [test1_AlplaPROD2.0_Read].[order].[Header] (nolock) h
/* get the line items to link to the headers */
left join
[test1_AlplaPROD2.0_Read].[order].[LineItem] (nolock) l on
l.HeaderId = h.id
/* get the releases to link to the headers */
left join
[test1_AlplaPROD2.0_Read].[order].[Release] (nolock) r on
r.LineItemId = l.id
where
--h.CreatedByEdi = 1
r.ReleaseState > 0
--and CustomerOrderNumber in ( '2365862', '2360391')
`;