Compare commits

...

9 Commits

41 changed files with 1189 additions and 832 deletions

View File

@@ -1,5 +1,77 @@
# All Changes to LST can be found below.
## [1.6.0](https://git.tuffraid.net/cowch/lst/compare/v1.5.0...v1.6.0) (2025-10-26)
### 📝 Testing Code
* **dock schedule fail:** failed attempt ad doing a dock schedule but leaving in here ([817a5c6](https://git.tuffraid.net/cowch/lst/commits/817a5c6876b338e4e0347eed94d0c2d9507e7ba3))
### 📈 Project changes
* **added biome linter in:** added in biom linter to assist ([2023c2f](https://git.tuffraid.net/cowch/lst/commits/2023c2fc35f8d57a6884d82b3710a03f3ccc57e7))
* **app:** config changes to bruno and incoming sqls ([f264c98](https://git.tuffraid.net/cowch/lst/commits/f264c98fbfccd4f1eb6dfdcb28b69903711a9e2b))
* **settings:** settings for biome ([dfff8fc](https://git.tuffraid.net/cowch/lst/commits/dfff8fc1667a5199a9f92bdbf7df1a1b19606b82))
### 📚 Documentation
* **api:** bruno endpoint saves ([705f29e](https://git.tuffraid.net/cowch/lst/commits/705f29e908b75e8ba8d09a9fc4a2b5745460babb))
* **bruno:** preprint in app added ([a2a8e0e](https://git.tuffraid.net/cowch/lst/commits/a2a8e0ef9f7086ba9d32bc4ec05a61e6904bfecc))
### 🛠️ Code Refactor
* **biome:** format changes ([dbe84d5](https://git.tuffraid.net/cowch/lst/commits/dbe84d5325291fb51f971426ec521c91eafa3537))
* **biome:** formats from biome ([27fa456](https://git.tuffraid.net/cowch/lst/commits/27fa45614e604a768c49dc4489fbf7a671364a7b))
* **biome:** more format changes ([255ceaa](https://git.tuffraid.net/cowch/lst/commits/255ceaab856e72435bbc3ebad37fb82c036f5208))
* **front end:** login fixes to account for the forced password change ([e99c409](https://git.tuffraid.net/cowch/lst/commits/e99c409cad049f781d4a52864f40264146a2bb49))
* **frontend:** tempt to stop the popup when redrected or coming to the page with no auth ([0fd777c](https://git.tuffraid.net/cowch/lst/commits/0fd777ccbdab2e8de8dcc02c134e01390bbc0d0a))
* **login:** added in a check for lastlogin and force reset password ([17e13d4](https://git.tuffraid.net/cowch/lst/commits/17e13d4604787d1473ae1e24ad4e9479087f6dce))
* **material checks:** added proper logging to capture it all for auditing later ([26b769f](https://git.tuffraid.net/cowch/lst/commits/26b769f4776df2833e3f27b02e5eedbc9f8693a6))
* **old app:** login migration to new app ([eb3fa4d](https://git.tuffraid.net/cowch/lst/commits/eb3fa4dd528427da49e2212bfa304ef9cdb06cc2))
* **plc connection zechetti:** added in more logging due to a weird issue with line 7 not sendin ([38edc62](https://git.tuffraid.net/cowch/lst/commits/38edc6214b353841a3414a66553446d4008ad54a))
* **printdelay:** added in a change to allow override the actualy time ([c59b6a1](https://git.tuffraid.net/cowch/lst/commits/c59b6a1ec27ecb8e5b6b08c8db7aee5bcb060801))
* **stats:** added in ram useage to the stats ([b9b0cd5](https://git.tuffraid.net/cowch/lst/commits/b9b0cd5c7010726532ef56ddab714308c8045b94))
* **wrapper:** changes to allow both controller and app to connect via wss:// ([da11270](https://git.tuffraid.net/cowch/lst/commits/da1127057cd766ec72316dee1ffcb11aed77904b))
### 🌟 Enhancements
* **admin:** users and roles added to the frontend to manage easier ([2142c06](https://git.tuffraid.net/cowch/lst/commits/2142c06ac3900aa70f1cf672b5a64102ed1c574f))
* **app:** order schdeuler ([94e1198](https://git.tuffraid.net/cowch/lst/commits/94e1198f6305751af7662a63e0ac21ac04f805d1))
* **frontend:** migrated old > new silo adjustments ([425f8f5](https://git.tuffraid.net/cowch/lst/commits/425f8f5f71d1dae1cf3a5d0307b3a2faeadb54b5))
* **labeling:** added printers and machine and other data for preprinting ([953af5e](https://git.tuffraid.net/cowch/lst/commits/953af5e0fea4cf0738a2bbfd3ee6ec46182b83dd))
* **labels:** added listener for old app to push all labels to the new app ([af079b8](https://git.tuffraid.net/cowch/lst/commits/af079b83069560f0a0d6f19c396a8238fba25e94))
* **migration:** dashboard migrated over ([2206a4d](https://git.tuffraid.net/cowch/lst/commits/2206a4d4baefdd770c83a03d68c9f5ac8f55a4c3))
* **migration:** dm moved ([ac9670d](https://git.tuffraid.net/cowch/lst/commits/ac9670d55340a3cc8e45d13ac1c09a056d06d1dd))
* **migration:** moved barcode generator ([fd9d774](https://git.tuffraid.net/cowch/lst/commits/fd9d774772aabb63fd69fe70302444fd2088d960))
* **migration:** moved changed log and properly added in the link to it ([0fe0a8f](https://git.tuffraid.net/cowch/lst/commits/0fe0a8f56a9833de1de4f8ba49f1d06b31e42ee8))
* **migration:** moved helper commands ([39c31aa](https://git.tuffraid.net/cowch/lst/commits/39c31aa1ec61231737cc4e7c3c33331ab562a808))
* **migration:** moved material helper over ([1da7b14](https://git.tuffraid.net/cowch/lst/commits/1da7b145a942dd64891511a8a63160748800de87))
* **migration:** moved ocp, ocme, wrapper stuff ([4ca20a0](https://git.tuffraid.net/cowch/lst/commits/4ca20a085efcb795bc312abff649a53132deac05))
* **migration:** moved rfid page ([f90a975](https://git.tuffraid.net/cowch/lst/commits/f90a975a5020a262109552019a13ead71271396c))
* **preprint:** added in preprint function to help with operations planning constraints ([282eab0](https://git.tuffraid.net/cowch/lst/commits/282eab01e15f81bcc407f45f1f3ffff056e0f27a))
* **settings:** added in settings ([a09ad87](https://git.tuffraid.net/cowch/lst/commits/a09ad8773c77b7b23ce98b3b3f6ce6122842f3ff))
* **v1 logger:** added in a logger to monitor the old app ([1d79195](https://git.tuffraid.net/cowch/lst/commits/1d79195d89cc31192c9998ef2b2f8ea501aff41e))
### 🐛 Bug fixes
* **admin:** corrections to sending over to test server for updates ([7964cda](https://git.tuffraid.net/cowch/lst/commits/7964cda197e42f2eadea3e636c2d910cb34c97bc))
* **barcodes:** moved to correct folder ([6a84da4](https://git.tuffraid.net/cowch/lst/commits/6a84da411770986f6f4c2088ebae169c688b28c1))
* **controller:** changed to actaully update both main and old app ([0d1f963](https://git.tuffraid.net/cowch/lst/commits/0d1f96333b11e6a2323e25552c10cc85d3c425af))
* **controller:** fix for updating iowa2 server ([358c41d](https://git.tuffraid.net/cowch/lst/commits/358c41deb41d418c5b1c6040269f2ed74e1782ff))
* **dmbuttons:** missed the uncomment here ([e620397](https://git.tuffraid.net/cowch/lst/commits/e62039793870a4d4b1f67adf256a9db2f6027e37))
* **loginform:** bug where the reset errror was not properly coming over ([414a21a](https://git.tuffraid.net/cowch/lst/commits/414a21a28719b50f61cc41056efc9b599491e048))
* **loginform:** error with password reset ([eae9436](https://git.tuffraid.net/cowch/lst/commits/eae9436f6d3aa8424043a426d5bbcc764967b3b6))
* **misc:** changes to several files for formatting ([b102112](https://git.tuffraid.net/cowch/lst/commits/b102112228bbf58b12bbca19cdf99483517b784d))
* **ocme:** corrected the urls for ocme ([f078cd6](https://git.tuffraid.net/cowch/lst/commits/f078cd6ceb9039e1f6e9b31f3e33ad446e65dd87))
* **old app:** correction for dev redirect if on localhost to be proper now ([70a2ff5](https://git.tuffraid.net/cowch/lst/commits/70a2ff5e845d9a8c460f4810f8de741fae32ad96))
* **printers:** missing logs for errors ([43abbd5](https://git.tuffraid.net/cowch/lst/commits/43abbd53f421988a13cbf8974a49d5ae5cbba1b9))
* **silos:** added only active machines, and ordered by location in asending ([d46ef92](https://git.tuffraid.net/cowch/lst/commits/d46ef922f3a6ddc36d5ddfcd94d424745f473a16))
## [1.5.0](https://git.tuffraid.net/cowch/lst/compare/v1.4.0...v1.5.0) (2025-10-07)

View File

@@ -0,0 +1,25 @@
meta {
name: CreateSilo Adjustment
type: http
seq: 3
}
post {
url: {{url}}/lst/old/api/logistics/createsiloadjustment
body: json
auth: inherit
}
body:json {
{
"warehouseId": 51,
"quantity": 60575,
"laneId": 31717,
"username":"matthes01"
}
}
settings {
encodeUrl: true
timeout: 0
}

View File

@@ -0,0 +1,8 @@
meta {
name: ocp
seq: 6
}
auth {
mode: inherit
}

View File

@@ -0,0 +1,16 @@
meta {
name: Update Printers
type: http
seq: 1
}
get {
url: {{url}}/lst/old/api/ocp/updateprinters
body: none
auth: inherit
}
settings {
encodeUrl: true
timeout: 0
}

View File

@@ -0,0 +1,8 @@
meta {
name: printers
seq: 1
}
auth {
mode: inherit
}

View File

@@ -0,0 +1,26 @@
meta {
name: Change user password
type: http
seq: 5
}
patch {
url: {{url}}/lst/api/admin/users/changePassword/:userId
body: json
auth: inherit
}
params:path {
userId: 0hlO48C7Jw1J804FxrCnonKjQ2zh48R6
}
body:json {
{
"password":"nova0511"
}
}
settings {
encodeUrl: true
timeout: 0
}

View File

@@ -0,0 +1,25 @@
meta {
name: Create user
type: http
seq: 4
}
post {
url: {{url}}/lst/api/admin/users
body: none
auth: inherit
}
body:json {
{
"username":"matthes01",
"name":"blake",
"email":"blake.matthes@alpla.com",
"password":"nova0511"
}
}
settings {
encodeUrl: true
timeout: 0
}

View File

@@ -0,0 +1,26 @@
meta {
name: Delete User
type: http
seq: 6
}
delete {
url: {{url}}/lst/api/admin/users/delete/:userId
body: json
auth: inherit
}
params:path {
userId: 0hlO48C7Jw1J804FxrCnonKjQ2zh48R6
}
body:json {
{
"password":"nova0511"
}
}
settings {
encodeUrl: true
timeout: 0
}

View File

@@ -21,4 +21,5 @@ body:json {
settings {
encodeUrl: true
timeout: 0
}

View File

@@ -5,7 +5,7 @@ meta {
}
patch {
url: {{url}}/lst/api/admin/:userID/grant
url: {{url}}/lst/api/admin/users/:userID/grant
body: json
auth: inherit
}

View File

@@ -5,7 +5,7 @@ meta {
}
patch {
url: {{url}}/lst/api/admin/:userID/revoke
url: {{url}}/lst/api/admin/users/:userID/revoke
body: json
auth: inherit
}

View File

@@ -1,5 +1,5 @@
vars {
url: https://usmcd1vms036.alpla.net
url: http://localhost:4200
session_cookie:
urlv2: http://localhost:3000
jwtV2:

View File

@@ -86,7 +86,7 @@ const main = async () => {
app.use(
basePath + "/old",
createProxyMiddleware({
target: `http://localhost:3000`,
target: `http://localhost:${process.env.V1PORT || "3000"}`, // change this to pull from the correct port
changeOrigin: true,
pathRewrite: (path, req) => {
// Remove the basePath + '/old' prefix from the path dynamically
@@ -113,7 +113,9 @@ const main = async () => {
"http://localhost:4173",
"http://localhost:4200",
"http://localhost:3000",
"http://localhost:3001",
"http://localhost:4000",
"http://localhost:4001",
env.BETTER_AUTH_URL, // prod
];

View File

@@ -0,0 +1,42 @@
import type { User } from "better-auth";
import { DrizzleQueryError } from "drizzle-orm";
import { auth } from "../../../../pkg/auth/auth.js";
import { tryCatch } from "../../../../pkg/utils/tryCatch.js";
export type NewUser = {
email: string;
password: string;
username: string;
statusCode: number;
message: string;
};
export const createNewUser = async (userData: NewUser) => {
const { data, error } = await tryCatch(
auth.api.createUser({
body: {
email: userData.email, // required
password: userData.password, // required
name: userData.username, // required
role: "user",
data: { username: userData.username },
},
}),
);
if (error) {
if (error instanceof DrizzleQueryError) {
// @ts-ignore
if (error?.cause.message.includes("unique constraint")) {
return {
statusCode: 400,
message: `${userData.username} already exists`,
};
}
}
return error;
}
return data;
};

View File

@@ -1,11 +1,9 @@
import type { Express, Request, Response } from "express";
import { requireAuth } from "../../pkg/middleware/authMiddleware.js";
import { mainServerSync } from "./controller/servers/matchServers.js";
//admin routes
import users from "./routes/getUserRoles.js";
import grantRoles from "./routes/grantRole.js";
import revokeRoles from "./routes/revokeRole.js";
import servers from "./routes/servers/serverRoutes.js";
//admin routes
import users from "./routes/users/userRoutes.js";
export const setupAdminRoutes = (app: Express, basePath: string) => {
app.use(
@@ -15,22 +13,10 @@ export const setupAdminRoutes = (app: Express, basePath: string) => {
app.use(
basePath + "/api/admin/users",
requireAuth("user", ["systemAdmin"]), // will pass bc system admin but this is just telling us we need this
requireAuth("user", ["systemAdmin", "admin"]), // will pass bc system admin but this is just telling us we need this
users,
);
app.use(
basePath + "/api/admin",
requireAuth("user", ["systemAdmin", "admin"]), // will pass bc system admin but this is just telling us we need this
grantRoles,
);
app.use(
basePath + "/api/admin",
requireAuth("user", ["systemAdmin", "admin"]), // will pass bc system admin but this is just telling us we need this
revokeRoles,
);
// run the sync only on startup
setTimeout(() => {
mainServerSync();

View File

@@ -1,52 +0,0 @@
import { Router } from "express";
import type { Request, Response } from "express";
import { tryCatch } from "../../../pkg/utils/tryCatch.js";
import { db } from "../../../pkg/db/db.js";
import { user } from "../../../pkg/db/schema/auth-schema.js";
import { userRoles } from "../../../pkg/db/schema/user_roles.js";
const router = Router();
router.post("/", async (req: Request, res: Response) => {
// should get all users
const { data: users, error: userError } = await tryCatch(
db.select().from(user)
);
if (userError) {
return res.status(500).json({
success: false,
message: "Failed to get users",
error: userError,
});
}
// should get all roles
const { data: userRole, error: userRoleError } = await tryCatch(
db.select().from(userRoles)
);
if (userRoleError) {
return res.status(500).json({
success: false,
message: "Failed to get userRoless",
error: userRoleError,
});
}
// add the roles and return
const usersWithRoles = users.map((user) => {
const roles = userRole
.filter((ur) => ur.userId === user.id)
.map((ur) => ({ module: ur.module, role: ur.role }));
return { ...user, roles };
});
return res
.status(200)
.json({ success: true, message: "User data", data: usersWithRoles });
});
export default router;

View File

@@ -0,0 +1,25 @@
import { type Request, type Response, Router } from "express";
import { auth } from "../../../../pkg/auth/auth.js";
const router = Router();
router.patch("/:userId", async (req: Request, res: Response) => {
const userId = req.params.userId;
const cookieHeader = req.headers.cookie ?? "";
const authorization = req.headers.authorization ?? "";
const data = await auth.api.setUserPassword({
body: {
newPassword: req.body.password, // required
userId: userId, // required
},
// This endpoint requires session cookies.
headers: {
cookie: cookieHeader,
authorization,
},
});
return res.status(200).json({ message: "Password was just changed." });
});
export default router;

View File

@@ -0,0 +1,20 @@
import { type Request, type Response, Router } from "express";
import { createNewUser, type NewUser } from "../../controller/users/newUser.js";
const router = Router();
router.post("/", async (req: Request, res: Response) => {
const body = req.body;
const user = (await createNewUser(body)) as NewUser;
if (user?.statusCode === 400) {
return res.status(user?.statusCode).json({
message: user?.message,
});
}
return res
.status(200)
.json({ message: `${body.username}, was just created` });
});
export default router;

View File

@@ -0,0 +1,24 @@
import { type Request, type Response, Router } from "express";
import { auth } from "../../../../pkg/auth/auth.js";
const router = Router();
router.delete("/:userId", async (req: Request, res: Response) => {
const userId = req.params.userId;
const cookieHeader = req.headers.cookie ?? "";
const authorization = req.headers.authorization ?? "";
const data = await auth.api.removeUser({
body: {
userId: userId, // required
},
// This endpoint requires session cookies.
headers: {
cookie: cookieHeader,
authorization,
},
});
return res.status(200).json({ message: "User was just deleted." });
});
export default router;

View File

@@ -0,0 +1,25 @@
import { type Request, type Response, Router } from "express";
import { auth } from "../../../../pkg/auth/auth.js";
const router = Router();
router.patch("/:userId", async (req: Request, res: Response) => {
const userId = req.params.userId;
const cookieHeader = req.headers.cookie ?? "";
const authorization = req.headers.authorization ?? "";
const data = await auth.api.setUserPassword({
body: {
newPassword: req.body.password, // required
userId: userId, // required
},
// This endpoint requires session cookies.
headers: {
cookie: cookieHeader,
authorization,
},
});
return res.status(200).json({ message: "Password was just changed." });
});
export default router;

View File

@@ -0,0 +1,52 @@
import type { Request, Response } from "express";
import { Router } from "express";
import { db } from "../../../../pkg/db/db.js";
import { user } from "../../../../pkg/db/schema/auth-schema.js";
import { userRoles } from "../../../../pkg/db/schema/user_roles.js";
import { tryCatch } from "../../../../pkg/utils/tryCatch.js";
const router = Router();
router.post("/", async (req: Request, res: Response) => {
// should get all users
const { data: users, error: userError } = await tryCatch(
db.select().from(user),
);
if (userError) {
return res.status(500).json({
success: false,
message: "Failed to get users",
error: userError,
});
}
// should get all roles
const { data: userRole, error: userRoleError } = await tryCatch(
db.select().from(userRoles),
);
if (userRoleError) {
return res.status(500).json({
success: false,
message: "Failed to get userRoless",
error: userRoleError,
});
}
// add the roles and return
const usersWithRoles = users.map((user) => {
const roles = userRole
.filter((ur) => ur.userId === user.id)
.map((ur) => ({ module: ur.module, role: ur.role }));
return { ...user, roles };
});
return res
.status(200)
.json({ success: true, message: "User data", data: usersWithRoles });
});
export default router;

View File

@@ -1,10 +1,9 @@
import type { Request, Response } from "express";
import { Router } from "express";
import z from "zod";
import { db } from "../../../pkg/db/db.js";
import { userRoles } from "../../../pkg/db/schema/user_roles.js";
import { createLogger } from "../../../pkg/logger/logger.js";
import { tryCatch } from "../../../pkg/utils/tryCatch.js";
import { db } from "../../../../pkg/db/db.js";
import { userRoles } from "../../../../pkg/db/schema/user_roles.js";
import { createLogger } from "../../../../pkg/logger/logger.js";
const roleSchema = z.object({
module: z.enum([

View File

@@ -2,10 +2,9 @@ import { and, eq } from "drizzle-orm";
import type { Request, Response } from "express";
import { Router } from "express";
import z from "zod";
import { db } from "../../../pkg/db/db.js";
import { userRoles } from "../../../pkg/db/schema/user_roles.js";
import { createLogger } from "../../../pkg/logger/logger.js";
import { tryCatch } from "../../../pkg/utils/tryCatch.js";
import { db } from "../../../../pkg/db/db.js";
import { userRoles } from "../../../../pkg/db/schema/user_roles.js";
import { createLogger } from "../../../../pkg/logger/logger.js";
const roleSchema = z.object({
module: z.enum([

View File

@@ -0,0 +1,51 @@
import { fromNodeHeaders } from "better-auth/node";
import type { Request, Response } from "express";
import { Router } from "express";
import { auth } from "../../../../pkg/auth/auth.js";
import { requireAuth } from "../../../../pkg/middleware/authMiddleware.js";
import changePassword from "./changeUserPassword.js";
import createUser from "./createUser.js";
import deleteUser from "./deleteUser.js";
import users from "./getUserRoles.js";
import grantRoles from "./grantRole.js";
import revokeRoles from "./revokeRole.js";
const router = Router();
router.use(
"/",
requireAuth("user", ["systemAdmin", "admin"]), // will pass bc system admin but this is just telling us we need this
grantRoles,
);
router.use(
"/new",
requireAuth("user", ["systemAdmin", "admin"]), // will pass bc system admin but this is just telling us we need this
createUser,
);
router.use(
"/",
requireAuth("user", ["systemAdmin", "admin"]), // will pass bc system admin but this is just telling us we need this
revokeRoles,
);
router.use(
"/",
requireAuth("user", ["systemAdmin", "admin"]), // will pass bc system admin but this is just telling us we need this
users,
);
router.use(
"/changePassword",
requireAuth("user", ["systemAdmin", "admin"]), // will pass bc system admin but this is just telling us we need this
changePassword,
);
router.use(
"/delete",
requireAuth("user", ["systemAdmin", "admin"]), // will pass bc system admin but this is just telling us we need this
deleteUser,
);
export default router;

View File

@@ -1,89 +1,93 @@
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "../db/db.js";
import { username, admin, apiKey, jwt } from "better-auth/plugins";
import { betterAuth } from "better-auth";
import * as rawSchema from "../db/schema/auth-schema.js";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { admin, apiKey, jwt, username } from "better-auth/plugins";
import type { User } from "better-auth/types";
import { eq } from "drizzle-orm";
import { db } from "../db/db.js";
import * as rawSchema from "../db/schema/auth-schema.js";
import { sendEmail } from "../utils/mail/sendMail.js";
export const schema = {
user: rawSchema.user,
session: rawSchema.session,
account: rawSchema.account,
verification: rawSchema.verification,
jwks: rawSchema.jwks,
apiKey: rawSchema.apikey, // 🔑 rename to apiKey
user: rawSchema.user,
session: rawSchema.session,
account: rawSchema.account,
verification: rawSchema.verification,
jwks: rawSchema.jwks,
apiKey: rawSchema.apikey, // 🔑 rename to apiKey
};
const RESET_EXPIRY_SECONDS = 3600; // 1 hour
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
schema,
}),
trustedOrigins: [
"*.alpla.net",
"http://localhost:5173",
"http://localhost:5500",
"http://localhost:4200",
"http://localhost:4000",
],
appName: "lst",
emailAndPassword: {
enabled: true,
minPasswordLength: 8, // optional config
resetPasswordTokenExpirySeconds: RESET_EXPIRY_SECONDS, // time in seconds
sendResetPassword: async ({ user, token }) => {
const frontendUrl = `${process.env.BETTER_AUTH_URL}/lst/app/user/resetpassword?token=${token}`;
const expiryMinutes = Math.floor(RESET_EXPIRY_SECONDS / 60);
const expiryText =
expiryMinutes >= 60
? `${expiryMinutes / 60} hour${
expiryMinutes === 60 ? "" : "s"
}`
: `${expiryMinutes} minutes`;
const emailData = {
email: user.email,
subject: "LST- Forgot password request",
template: "forgotPassword",
context: {
username: user.name,
email: user.email,
url: frontendUrl,
expiry: expiryText,
},
};
await sendEmail(emailData);
},
// onPasswordReset: async ({ user }, request) => {
// // your logic here
// console.log(`Password for user ${user.email} has been reset.`);
// },
},
plugins: [
//jwt({ jwt: { expirationTime: "1h" } }),
apiKey(),
admin(),
username(),
],
session: {
expiresIn: 60 * 60,
updateAge: 60 * 5,
freshAge: 60 * 2,
cookieCache: {
enabled: true,
maxAge: 5 * 60, // Cache duration in seconds
},
},
events: {
async onSignInSuccess({ user }: { user: User }) {
await db
.update(schema.user)
.set({ lastLogin: new Date() })
.where(eq(schema.user.id, user.id));
},
},
database: drizzleAdapter(db, {
provider: "pg",
schema,
}),
trustedOrigins: [
"*.alpla.net",
"http://localhost:5173",
"http://localhost:5500",
"http://localhost:4200",
"http://localhost:4000",
],
appName: "lst",
emailAndPassword: {
enabled: true,
minPasswordLength: 8, // optional config
resetPasswordTokenExpirySeconds: RESET_EXPIRY_SECONDS, // time in seconds
sendResetPassword: async ({ user, token }) => {
const frontendUrl = `${process.env.BETTER_AUTH_URL}/lst/app/user/resetpassword?token=${token}`;
const expiryMinutes = Math.floor(RESET_EXPIRY_SECONDS / 60);
const expiryText =
expiryMinutes >= 60
? `${expiryMinutes / 60} hour${expiryMinutes === 60 ? "" : "s"}`
: `${expiryMinutes} minutes`;
const emailData = {
email: user.email,
subject: "LST- Forgot password request",
template: "forgotPassword",
context: {
username: user.name,
email: user.email,
url: frontendUrl,
expiry: expiryText,
},
};
await sendEmail(emailData);
},
// onPasswordReset: async ({ user }, request) => {
// // your logic here
// console.log(`Password for user ${user.email} has been reset.`);
// },
},
plugins: [
//jwt({ jwt: { expirationTime: "1h" } }),
apiKey(),
admin(),
username(),
],
session: {
expiresIn: 60 * 60,
updateAge: 60 * 5,
freshAge: 60 * 2,
cookieCache: {
enabled: true,
maxAge: 5 * 60,
},
},
cookie: {
path: "/lst/app",
sameSite: "lax",
secure: false,
httpOnly: true,
},
events: {
async onSignInSuccess({ user }: { user: User }) {
await db
.update(schema.user)
.set({ lastLogin: new Date() })
.where(eq(schema.user.id, user.id));
},
},
});
export type Auth = typeof auth;

View File

@@ -1,7 +1,7 @@
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { redirect, useNavigate, useRouter } from "@tanstack/react-router";
import { createAuthClient } from "better-auth/client";
import { usernameClient } from "better-auth/client/plugins";
import { adminClient, usernameClient } from "better-auth/client/plugins";
import { useEffect } from "react";
import { create } from "zustand";
import { api } from "./axiosAPI";
@@ -112,7 +112,7 @@ export async function checkUserAccess({
// ---- BETTER AUTH CLIENT ----
export const authClient = createAuthClient({
baseURL: `${window.location.origin}/lst/api/auth`,
plugins: [usernameClient()],
plugins: [usernameClient(), adminClient()],
options: {
autoPopup: false,
requireAuth: false,

View File

@@ -59,7 +59,7 @@ export default function ExpandedRow({ row }: { row: any }) {
// user,
// });
try {
const result = await api.patch(`/api/admin/${user.id}/grant`, {
const result = await api.patch(`/api/admin/users/${user.id}/grant`, {
module: module,
role: role,
});
@@ -83,7 +83,7 @@ export default function ExpandedRow({ row }: { row: any }) {
const onDeleteRole = async (module: string) => {
try {
const result = await api.patch(`/api/admin/${user.id}/revoke`, {
const result = await api.patch(`/api/admin/users/${user.id}/revoke`, {
module: module,
});

View File

@@ -3,6 +3,7 @@ import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarMenuButton,
SidebarMenuItem,
SidebarTrigger,
} from "../../../../../components/ui/sidebar";
@@ -51,7 +52,9 @@ export function AppSidebar() {
</SidebarContent>
<SidebarFooter>
<SidebarMenuItem>
<Link to={"/changelog"}>Changelog</Link>
<SidebarMenuButton asChild>
<Link to={"/changelog"}>Changelog</Link>
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarTrigger />
</SidebarFooter>

View File

@@ -0,0 +1,3 @@
export default function MaterialHelperPage() {
return <div>materialHelperPage</div>;
}

View File

@@ -7,10 +7,11 @@ 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 { useAuth } from "@/lib/authClient";
import { LstCard } from "../../extendedUi/LstCard";
export default function Comment(data: any) {
const token = localStorage.getItem("auth_token");
const { session } = useAuth();
const [isSubmitting, setIsSubmitting] = useState(false);
const router = useRouter();
@@ -27,8 +28,8 @@ export default function Comment(data: any) {
{
comment: value.comment,
key: data.id.split("&")[1].replace("amp;", ""),
username: session?.user.username,
},
{ headers: { Authorization: `Bearer ${token}` } },
);
if (res.data.success) {

View File

@@ -43,6 +43,7 @@ export default function SiloCard(data: any) {
quantity: parseFloat(value.newLevel),
warehouseId: silo.WarehouseID,
laneId: silo.LocationID,
username: session?.user.username,
};
try {

View File

@@ -1,153 +1,143 @@
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useForm } from "@tanstack/react-form";
import axios from "axios";
import { format } from "date-fns";
import { useState } from "react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export default function ExportInventoryData() {
const [open, setOpen] = useState(false);
const [saving, setSaving] = useState(false);
const [open, setOpen] = useState(false);
const [saving, setSaving] = useState(false);
const form = useForm({
defaultValues: {
age: "",
},
onSubmit: async ({ value }) => {
setSaving(true);
try {
const res = await axios.get(
`/api/logistics/getcyclecount?age=${value.age}`,
{
responseType: "blob",
}
);
const form = useForm({
defaultValues: {
age: "",
},
onSubmit: async ({ value }) => {
setSaving(true);
try {
const res = await axios.get(
`/lst/old/api/logistics/getcyclecount?age=${value.age}`,
{
responseType: "blob",
},
);
const blob = new Blob([res.data], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
});
const blob = new Blob([res.data], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
});
const link = document.createElement("a");
link.href = window.URL.createObjectURL(blob);
link.download = `CycleCount-${format(new Date(Date.now()), "M-d-yyyy")}.xlsx`; // You can make this dynamic
document.body.appendChild(link);
link.click();
const link = document.createElement("a");
link.href = window.URL.createObjectURL(blob);
link.download = `CycleCount-${format(new Date(Date.now()), "M-d-yyyy")}.xlsx`; // You can make this dynamic
document.body.appendChild(link);
link.click();
// Clean up
document.body.removeChild(link);
window.URL.revokeObjectURL(link.href);
toast.success(`File Downloaded`);
setSaving(false);
setOpen(false);
form.reset();
} catch (error) {
console.log(error);
console.log(`There was an error getting cycle counts.`);
}
},
});
// Clean up
document.body.removeChild(link);
window.URL.revokeObjectURL(link.href);
toast.success(`File Downloaded`);
setSaving(false);
setOpen(false);
form.reset();
} catch (error) {
console.log(error);
console.log(`There was an error getting cycle counts.`);
}
},
});
return (
<div>
<Dialog
open={open}
onOpenChange={(isOpen) => {
if (!open) {
form.reset();
}
setOpen(isOpen);
// toast.message("Model was something", {
// description: isOpen ? "Modal is open" : "Modal is closed",
// });
}}
>
<DialogTrigger asChild>
<Button variant="outline">Export Inventory Check</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Export Inventory lane check</DialogTitle>
<DialogDescription>
Exports all lanes based on the age you enter, except
empty lanes.
</DialogDescription>
</DialogHeader>
<form
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<div>
<>
<form.Field
name="age"
// 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 flex flex-row">
<Label htmlFor="active">
Age
</Label>
<Input
className="ml-2"
name={field.name}
onBlur={field.handleBlur}
type="number"
onChange={(e) =>
field.handleChange(
e.target.value
)
}
/>
</div>
);
}}
/>
</>
</div>
return (
<div>
<Dialog
open={open}
onOpenChange={(isOpen) => {
if (!open) {
form.reset();
}
setOpen(isOpen);
// toast.message("Model was something", {
// description: isOpen ? "Modal is open" : "Modal is closed",
// });
}}
>
<DialogTrigger asChild>
<Button variant="outline">Export Inventory Check</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Export Inventory lane check</DialogTitle>
<DialogDescription>
Exports all lanes based on the age you enter, except empty lanes.
</DialogDescription>
</DialogHeader>
<form
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<div>
<>
<form.Field
name="age"
// 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 flex flex-row">
<Label htmlFor="active">Age</Label>
<Input
className="ml-2"
name={field.name}
onBlur={field.handleBlur}
type="number"
onChange={(e) => field.handleChange(e.target.value)}
/>
</div>
);
}}
/>
</>
</div>
<DialogFooter>
<div className="flex justify-end mt-2">
<Button onClick={() => setOpen(false)}>
Close
</Button>
<Button
type="submit"
disabled={saving}
onClick={form.handleSubmit}
>
{saving ? (
<>
<span>Saving....</span>
</>
) : (
<span>Save setting</span>
)}
</Button>
</div>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
</div>
);
<DialogFooter>
<div className="flex justify-end mt-2">
<Button onClick={() => setOpen(false)}>Close</Button>
<Button
type="submit"
disabled={saving}
onClick={form.handleSubmit}
>
{saving ? (
<>
<span>Saving....</span>
</>
) : (
<span>Save setting</span>
)}
</Button>
</div>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
</div>
);
}

View File

@@ -20,6 +20,7 @@ import { useAuth, useLogout } from "../../../lib/authClient";
import { AddCards } from "./-components/dashboard/AddCards";
import { AppSidebar } from "./-components/layout/lst-sidebar";
import DMButtons from "./-components/logistics/dm/DMButtons";
import ExportInventoryData from "./-components/logistics/warehouse/ExportInventoryData";
export const Route = createFileRoute("/_old/old")({
component: RouteComponent,
@@ -39,7 +40,7 @@ function RouteComponent() {
{location.pathname === "/lst/app/old" ||
(location.pathname === "/lst/app/old/" && (
<div className="m-auto pr-2 flex flex-row gap-2">
{/* <ExportInventoryData /> */}
<ExportInventoryData />
<AddCards />
</div>
))}

View File

@@ -6,17 +6,17 @@ const { sign, verify } = jwt;
export const authMiddleware: MiddlewareHandler = async (c, next) => {
console.log("middleware checked");
const cookieHeader = c.req.header("Cookie");
if (!cookieHeader) return c.json({ error: "Unauthorized" }, 401);
// const cookieHeader = c.req.header("Cookie");
// if (!cookieHeader) return c.json({ error: "Unauthorized" }, 401);
const res = await axios.get(`${process.env.LST_BASE_URL}/api/user/me`, {
headers: { Cookie: cookieHeader },
});
// const res = await axios.get(`${process.env.LST_BASE_URL}/api/user/me`, {
// headers: { Cookie: cookieHeader },
// });
if (res.status === 401) return c.json({ error: "Unauthorized" }, 401);
// if (res.status === 401) return c.json({ error: "Unauthorized" }, 401);
//const user = await resp.json();
c.set("user", res.data.user);
// //const user = await resp.json();
// c.set("user", res.data.user);
return next();
};

View File

@@ -84,26 +84,26 @@ interface UserRole {
const hasCorrectRole = (requiredRole: string[], module: string) =>
createMiddleware(async (c, next) => {
const cookieHeader = c.req.header("Cookie");
if (!cookieHeader) return c.json({ error: "Unauthorized" }, 401);
// const cookieHeader = c.req.header("Cookie");
// if (!cookieHeader) return c.json({ error: "Unauthorized" }, 401);
const res = await axios.get(`${process.env.LST_BASE_URL}/api/user/roles`, {
headers: { Cookie: cookieHeader },
});
// const res = await axios.get(`${process.env.LST_BASE_URL}/api/user/roles`, {
// headers: { Cookie: cookieHeader },
// });
const currentRoles: UserRole[] = res.data.data;
const canAccess = currentRoles.some(
(r) => r.module === module && requiredRole.includes(r.role),
);
if (!canAccess) {
return c.json(
{
error: "Unauthorized",
message: `You do not have access to ${module}`,
},
400,
);
}
// const currentRoles: UserRole[] = res.data.data;
// const canAccess = currentRoles.some(
// (r) => r.module === module && requiredRole.includes(r.role),
// );
// if (!canAccess) {
// return c.json(
// {
// error: "Unauthorized",
// message: `You do not have access to ${module}`,
// },
// 400,
// );
// }
return next();
});

View File

@@ -44,7 +44,7 @@ app.openapi(
try {
try {
//return apiReturn(c, true, access?.message, access?.data, 200);
const createSiloAdj = await createSiloAdjustment(data, c.get("user"));
const createSiloAdj = await createSiloAdjustment(data, data.username);
return c.json(
{

View File

@@ -55,7 +55,7 @@ app.openapi(
adjId,
data.comment,
data.key,
c.get("user"),
data.username,
);
console.log(addComment);

View File

@@ -6,289 +6,273 @@ import { machineCheck } from "../../../sqlServer/querys/ocp/machineId.js";
import { mmQuery } from "../../../sqlServer/querys/ocp/mainMaterial.js";
export const isMainMatStaged = async (lot: any) => {
const set = serverSettings.length === 0 ? [] : serverSettings;
// make staged false by deefault and error logged if theres an issue
let isStaged = { message: "Material is staged", success: true };
const set = serverSettings.length === 0 ? [] : serverSettings;
// make staged false by deefault and error logged if theres an issue
let isStaged = { message: "Material is staged", success: true };
const { data, error } = (await tryCatch(
query(
machineCheck.replace("where Active = 1 and [Location] = [loc]", ""),
"check machine needs mm"
)
)) as any;
const { data, error } = (await tryCatch(
query(
machineCheck.replace("where Active = 1 and [Location] = [loc]", ""),
"check machine needs mm",
),
)) as any;
const machine = data.data.filter(
(m: any) => m.HumanReadableId === lot.machineID
);
// we have a check on ksc side to ignore the tetra machine for now as its not updating in 2.0
if (!machine[0].StagingMainMaterialMandatory) {
createLog(
"info",
"mainMaterial",
"ocp",
`The machine dose not require mm to print and book in.`
);
return {
message: "Machine dose not require material to be staged",
success: true,
};
}
const machine = data.data.filter(
(m: any) => m.HumanReadableId === lot.machineID,
);
// we have a check on ksc side to ignore the tetra machine for now as its not updating in 2.0
if (!machine[0].StagingMainMaterialMandatory) {
createLog(
"info",
"mainMaterial",
"ocp",
`The machine dose not require mm to print and book in.`,
);
return {
message: "Machine dose not require material to be staged",
success: true,
};
}
// strangly the lot is not always sending over in slc so adding this in for now to see what line is cauing this issue
if (!lot) {
createLog(
"info",
"mainMaterial",
"ocp",
"No lot was passed correctly."
);
return isStaged;
}
// strangly the lot is not always sending over in slc so adding this in for now to see what line is cauing this issue
if (!lot) {
createLog("info", "mainMaterial", "ocp", "No lot was passed correctly.");
return isStaged;
}
if (typeof lot !== "object" || lot === null || Array.isArray(lot)) {
createLog(
"info",
"mainMaterial",
"ocp",
`The lot sent over is not an object: ${JSON.stringify(lot)}`
);
return isStaged;
}
if (typeof lot !== "object" || lot === null || Array.isArray(lot)) {
createLog(
"info",
"mainMaterial",
"ocp",
`The lot sent over is not an object: ${JSON.stringify(lot)}`,
);
return isStaged;
}
const updateQuery = mmQuery.replaceAll("[lotNumber]", lot.lot);
const updateQuery = mmQuery.replaceAll("[lotNumber]", lot.lot);
try {
const r: any = await query(updateQuery, "Main Material Check");
try {
const r: any = await query(updateQuery, "Main Material Check");
const res: any = r.data;
const res: any = r.data;
// if (res[0].Staged >= 1) {
// isStaged = true;
// }
// if (res[0].Staged >= 1) {
// isStaged = true;
// }
type CheckConditionArgs = {
results: any[];
filterFn: (n: any) => boolean;
failCondition: (n: any) => boolean;
failMessage: string;
successMessage: string;
lot: { lot: string | number };
};
type CheckConditionArgs = {
results: any[];
filterFn: (n: any) => boolean;
failCondition: (n: any) => boolean;
failMessage: string;
successMessage: string;
lot: { lot: string | number };
};
const checkCondition = ({
results,
filterFn,
failCondition,
failMessage,
successMessage,
lot,
}: CheckConditionArgs): { message: string; success: boolean } => {
const subset = results.filter(filterFn);
const checkCondition = ({
results,
filterFn,
failCondition,
failMessage,
successMessage,
lot,
}: CheckConditionArgs): { message: string; success: boolean } => {
const subset = results.filter(filterFn);
if (subset.some(failCondition)) {
const failing = subset.filter(failCondition);
createLog(
"info",
"mainMaterial",
"ocp",
`lot: ${lot.lot}, is missing: ${failing
.map(
(o: any) =>
`${o.MaterialHumanReadableId} - ${o.MaterialDescription}`
)
.join(",\n ")} ${failMessage}`
);
return {
message: `lot: ${lot.lot}, is missing: ${failing
.map(
(o: any) =>
`${o.MaterialHumanReadableId} - ${o.MaterialDescription}`
)
.join(",\n ")} ${failMessage}`,
success: false,
};
} else {
createLog(
"info",
"mainMaterial",
"ocp",
`lot: ${lot.lot}, ${JSON.stringify(results)}`
);
return { message: successMessage, success: true };
}
};
if (subset.some(failCondition)) {
const failing = subset.filter(failCondition);
createLog(
"info",
"mainMaterial",
"ocp",
`lot: ${lot.lot}, is missing: ${failing
.map(
(o: any) =>
`${o.MaterialHumanReadableId} - ${o.MaterialDescription}`,
)
.join(",\n ")} ${failMessage}`,
);
return {
message: `lot: ${lot.lot}, is missing: ${failing
.map(
(o: any) =>
`${o.MaterialHumanReadableId} - ${o.MaterialDescription}`,
)
.join(",\n ")} ${failMessage}`,
success: false,
};
} else {
createLog(
"info",
"mainMaterial",
"ocp",
`lot: ${lot.lot}, ${JSON.stringify(results)}`,
);
return { message: successMessage, success: true };
}
};
createLog("info", "mainMaterial", "ocp", `Maint material query ran.`);
createLog("info", "mainMaterial", "ocp", `Maint material query ran.`);
const mainMaterial = res.find((n: any) => n.IsMainMaterial);
if (mainMaterial?.noMMShortage === "noMM") {
createLog(
"info",
"mainMaterial",
"ocp",
`Main material: ${mainMaterial.MaterialHumanReadableId} - ${mainMaterial.MaterialDescription}: is not staged for ${lot.lot}`
);
return {
message: `Main material: ${mainMaterial.MaterialHumanReadableId} - ${mainMaterial.MaterialDescription}: is not staged for ${lot.lot}`,
success: false,
};
}
const mainMaterial = res.find((n: any) => n.IsMainMaterial);
if (mainMaterial?.Staged === 1) {
createLog(
"info",
"mainMaterial",
"ocp",
`Main material: ${mainMaterial.MaterialHumanReadableId} - ${mainMaterial.MaterialDescription}: is staged for ${lot.lot}`,
);
return {
message: `Main material: ${mainMaterial.MaterialHumanReadableId} - ${mainMaterial.MaterialDescription}: is staged for ${lot.lot}`,
success: true,
};
}
if (mainMaterial?.noMMShortage === "noMM") {
createLog(
"info",
"mainMaterial",
"ocp",
`Main material: ${mainMaterial.MaterialHumanReadableId} - ${mainMaterial.MaterialDescription}: is not staged for ${lot.lot}`,
);
return {
message: `Main material: ${mainMaterial.MaterialHumanReadableId} - ${mainMaterial.MaterialDescription}: is not staged for ${lot.lot}`,
success: false,
};
}
// we need to filter the color stuff and then look for includes instead of a standard name. this way we can capture a everything and not a single type
// for manual consume color if active to check colors
const checkColorSetting = set.filter((n) => n.name === "checkColor");
// we need to filter the color stuff and then look for includes instead of a standard name. this way we can capture a everything and not a single type
// for manual consume color if active to check colors
const checkColorSetting = set.filter((n) => n.name === "checkColor");
// 2. Auto color
if (checkColorSetting[0].value === "1") {
// auto check
// 2. Auto color
const autoColor = checkCondition({
results: res,
lot,
filterFn: (n) =>
n.isManual &&
!("noPKGAutoShortage" in n) &&
!("noPKGManualShortage" in n), // pool = non-main, auto
failCondition: (n) => n.autoConsumeCheck === "autoConsumeNOK", // column = autoConsumeCheck
failMessage: "for autoconsume",
successMessage: "auto color is good",
});
if (!autoColor.success) return autoColor;
createLog(
"info",
"mainMaterial",
"ocp",
`Auto Color: ${JSON.stringify(autoColor)}`
);
// 2. Auto color
if (checkColorSetting[0].value === "1") {
// auto check
// 2. Auto color
const autoColor = checkCondition({
results: res,
lot,
filterFn: (n) =>
n.isManual &&
!("noPKGAutoShortage" in n) &&
!("noPKGManualShortage" in n), // pool = non-main, auto
failCondition: (n) => n.autoConsumeCheck === "autoConsumeNOK", // column = autoConsumeCheck
failMessage: "for autoconsume",
successMessage: "auto color is good",
});
if (!autoColor.success) return autoColor;
createLog(
"info",
"mainMaterial",
"ocp",
`Auto Color: ${JSON.stringify(autoColor)}`,
);
// 3. Manual color
const manualColor = checkCondition({
results: res,
lot,
filterFn: (n) =>
!n.IsMainMaterial &&
n.isManual &&
!("noPKGAutoShortage" in n) &&
!("noPKGManualShortage" in n), // pool = non-main, manual
failCondition: (n) => n.noManualShortage === "noOK", // column = noManualShortage
failMessage: "for manual material",
successMessage: "manual color is good",
});
if (!manualColor.success) return manualColor;
createLog(
"info",
"mainMaterial",
"ocp",
`Manual Color: ${JSON.stringify(manualColor)}`
);
} else {
createLog(
"info",
"mainMaterial",
"ocp",
"Color check is not active."
);
}
// 3. Manual color
const manualColor = checkCondition({
results: res,
lot,
filterFn: (n) =>
!n.IsMainMaterial &&
n.isManual &&
!("noPKGAutoShortage" in n) &&
!("noPKGManualShortage" in n), // pool = non-main, manual
failCondition: (n) => n.noManualShortage === "noOK", // column = noManualShortage
failMessage: "for manual material",
successMessage: "manual color is good",
});
if (!manualColor.success) return manualColor;
createLog(
"info",
"mainMaterial",
"ocp",
`Manual Color: ${JSON.stringify(manualColor)}`,
);
} else {
createLog("info", "mainMaterial", "ocp", "Color check is not active.");
}
// // if we want to check the packaging
const checkPKGSetting = set.filter((n) => n.name === "checkPKG");
if (checkPKGSetting[0].value === "1") {
const pkgAuto = checkCondition({
results: res,
lot,
filterFn: (n) =>
!n.IsMainMaterial &&
!n.isManual &&
"noPKGAutoShortage" in n,
failCondition: (n) => n.noPKGAutoShortage === "noAutoPkg",
failMessage: "for pkg",
successMessage: "auto PKG is good",
});
if (!pkgAuto.success) return pkgAuto;
createLog(
"info",
"mainMaterial",
"ocp",
`PKG Auto: ${JSON.stringify(pkgAuto)}`
);
// 5. Packaging manual
const pkgManual = checkCondition({
results: res,
lot,
filterFn: (n) =>
!n.IsMainMaterial &&
n.isManual &&
"noPKGManualShortage" in n,
failCondition: (n) => n.noPKGManualShortage === "noManPkg",
failMessage: "for pkg",
successMessage: "manual PKG is good",
});
// // if we want to check the packaging
const checkPKGSetting = set.filter((n) => n.name === "checkPKG");
if (checkPKGSetting[0].value === "1") {
const pkgAuto = checkCondition({
results: res,
lot,
filterFn: (n) =>
!n.IsMainMaterial && !n.isManual && "noPKGAutoShortage" in n,
failCondition: (n) => n.noPKGAutoShortage === "noAutoPkg",
failMessage: "for pkg",
successMessage: "auto PKG is good",
});
if (!pkgAuto.success) return pkgAuto;
createLog(
"info",
"mainMaterial",
"ocp",
`PKG Auto: ${JSON.stringify(pkgAuto)}`,
);
// 5. Packaging manual
const pkgManual = checkCondition({
results: res,
lot,
filterFn: (n) =>
!n.IsMainMaterial && n.isManual && "noPKGManualShortage" in n,
failCondition: (n) => n.noPKGManualShortage === "noManPkg",
failMessage: "for pkg",
successMessage: "manual PKG is good",
});
if (!pkgManual.success) return pkgManual;
createLog(
"info",
"mainMaterial",
"ocp",
`PKG Manual: ${JSON.stringify(pkgManual)}`
);
} else {
createLog(
"info",
"mainMaterial",
"ocp",
"PKG check is not active."
);
}
if (!pkgManual.success) return pkgManual;
createLog(
"info",
"mainMaterial",
"ocp",
`PKG Manual: ${JSON.stringify(pkgManual)}`,
);
} else {
createLog("info", "mainMaterial", "ocp", "PKG check is not active.");
}
// manual pkg
if (checkPKGSetting[0].value === "1") {
const packagingCheck = res.filter(
(n: any) =>
!n.IsMainMaterial &&
n.isManual &&
"noPKGManualShortage" in n
);
if (
packagingCheck.some(
(n: any) => n.noPKGManualShortage === "noManPkg"
)
) {
createLog(
"info",
"mainMaterial",
"ocp",
`lot: ${lot.lot}, is missing: ${packagingCheck
.map(
(o: any) =>
`${o.MaterialHumanReadableId} - ${o.MaterialDescription}`
)
.join(",\n ")} for pkg`
);
return (isStaged = {
message: `lot: ${lot.lot}, is missing: ${packagingCheck
.map(
(o: any) =>
`${o.MaterialHumanReadableId} - ${o.MaterialDescription}`
)
.join(",\n ")} for pkg`,
success: false,
});
}
} else {
createLog(
"info",
"mainMaterial",
"ocp",
"PKG check is not active."
);
}
} catch (err) {
createLog(
"error",
"mainMaterial",
"ocp",
`Error from running the Main Material query: ${err}`
);
}
return isStaged;
// manual pkg
if (checkPKGSetting[0].value === "1") {
const packagingCheck = res.filter(
(n: any) =>
!n.IsMainMaterial && n.isManual && "noPKGManualShortage" in n,
);
if (
packagingCheck.some((n: any) => n.noPKGManualShortage === "noManPkg")
) {
createLog(
"info",
"mainMaterial",
"ocp",
`lot: ${lot.lot}, is missing: ${packagingCheck
.map(
(o: any) =>
`${o.MaterialHumanReadableId} - ${o.MaterialDescription}`,
)
.join(",\n ")} for pkg`,
);
return (isStaged = {
message: `lot: ${lot.lot}, is missing: ${packagingCheck
.map(
(o: any) =>
`${o.MaterialHumanReadableId} - ${o.MaterialDescription}`,
)
.join(",\n ")} for pkg`,
success: false,
});
}
} else {
createLog("info", "mainMaterial", "ocp", "PKG check is not active.");
}
} catch (err) {
createLog(
"error",
"mainMaterial",
"ocp",
`Error from running the Main Material query: ${err}`,
);
}
return isStaged;
};

View File

@@ -1,285 +1,275 @@
import net from "net";
import { pausePrinter } from "../../utils/pausePrinter.js";
import { addHours, differenceInSeconds } from "date-fns";
import { printerUpdate } from "./printerStatUpdate.js";
import net from "net";
import { timeZoneFix } from "../../../../globalUtils/timeZoneFix.js";
import { createLog } from "../../../logger/logger.js";
import { pausePrinter } from "../../utils/pausePrinter.js";
import { unPausePrinter } from "../../utils/unpausePrinter.js";
import { labelingProcess } from "../labeling/labelProcess.js";
import { timeZoneFix } from "../../../../globalUtils/timeZoneFix.js";
import { autoLabelCreated } from "../labeling/labelRatio.js";
import { printerUpdate } from "./printerStatUpdate.js";
let logLevel: string = process.env.LOG_LEVEL || "info";
let errorCheck = false;
export const printerStatus = async (p: any) => {
/**
* Checks each printer to see what the current state is
*/
createLog("debug", "ocp", "ocp", `Printer cycling`);
/**
* Checks each printer to see what the current state is
*/
createLog("debug", "ocp", "ocp", `Printer cycling`);
const printer = new net.Socket();
const printer = new net.Socket();
return new Promise((resolve) => {
// connect to the printer, and check its status
printer.connect(p.port, p.ipAddress, async () => {
// write the message to the printer below gives us a feedback of the printer
printer.write("~HS");
});
return new Promise((resolve) => {
// connect to the printer, and check its status
printer.connect(p.port, p.ipAddress, async () => {
// write the message to the printer below gives us a feedback of the printer
printer.write("~HS");
});
// read the data from the printer
printer.on("data", async (data) => {
const res = data.toString();
// read the data from the printer
printer.on("data", async (data) => {
const res = data.toString();
// turn the data into an array to make it more easy to deal with
const tmp = res.split(",");
// turn the data into an array to make it more easy to deal with
const tmp = res.split(",");
//--------------- time stuff-----------------------------------------------------------------
// get last time printed
const lastTime = new Date(p.lastTimePrinted).toISOString();
// console.log(lastTime);
//--------------- time stuff-----------------------------------------------------------------
// get last time printed
const lastTime = new Date(p.lastTimePrinted).toISOString();
// console.log(lastTime);
// current time?
// current time?
/**
*
* add the time zone to the settings db
*/
// const currentTime = addHours(
// new Date(Date.now()),
// -6
// ).toISOString();
/**
*
* add the time zone to the settings db
*/
// const currentTime = addHours(
// new Date(Date.now()),
// -6
// ).toISOString();
const currentTime = timeZoneFix();
const currentTime = timeZoneFix();
let timeBetween = 0;
// if this is our first time printing pause the printer to start the timer, else we just update the time between timer
if (lastTime === undefined) {
printer.end();
printerUpdate(p, 8);
pausePrinter(p);
resolve({ success: true, message: "First Time printing" });
} else {
timeBetween = differenceInSeconds(currentTime, lastTime);
}
let timeBetween = 0;
// if this is our first time printing pause the printer to start the timer, else we just update the time between timer
if (lastTime === undefined) {
printer.end();
printerUpdate(p, 8);
pausePrinter(p);
resolve({ success: true, message: "First Time printing" });
} else {
timeBetween = differenceInSeconds(currentTime, lastTime);
}
// --- end time ---
// --- end time ---
// --- printer logic ---
createLog(
"debug",
"ocp",
"ocp",
`${p.name}: timeBetween: ${timeBetween}, delay ${parseInt(
p.printDelay
)}, ${currentTime}... ${lastTime}`
);
// --- printer logic ---
createLog(
"debug",
"ocp",
"ocp",
`${p.name}: timeBetween: ${timeBetween}, delay ${parseInt(
p.printDelay,
)}, ${currentTime}... ${lastTime}`,
);
if (tmp[2] === "0" && tmp[4] !== "000") {
// unpaused and printing labels - reset timer
createLog(
"debug",
"ocp",
"ocp",
`Unpaused and printing labels, time remaing ${differenceInSeconds(
parseInt(p.printDelay),
timeBetween
)}`
);
if (tmp[2] === "0" && tmp[4] !== "000") {
// unpaused and printing labels - reset timer
createLog(
"info",
"ocp",
"ocp",
`Unpaused and printing labels, time remaing ${differenceInSeconds(
parseInt(p.printDelay),
timeBetween,
)}`,
);
// update last time printed in the array
printerUpdate(p, 1);
} else if (tmp[2] === "1" && tmp[4] !== "000") {
// was paused or label sent from somewhere else
createLog(
"info",
"ocp",
"ocp",
`${
p.name
} paused to soon, unpausing, remaining time: ${differenceInSeconds(
parseInt(p.printDelay),
timeBetween
)}`
);
// update last time printed in the array
printerUpdate(p, 1);
} else if (tmp[2] === "1" && tmp[4] !== "000") {
// was paused or label sent from somewhere else
createLog(
"info",
"ocp",
"ocp",
`${
p.name
} paused to soon, unpausing, remaining time: ${differenceInSeconds(
parseInt(p.printDelay),
timeBetween,
)}`,
);
// reset the timer for this printer as well other labels shouldnt be sent but if we send them ok
printerUpdate(p, 2);
// reset the timer for this printer as well other labels shouldnt be sent but if we send them ok
printerUpdate(p, 2);
unPausePrinter(p);
} else if (tmp[2] === "0" && timeBetween < parseInt(p.printDelay)) {
// was unpaused to soon so repause it
createLog(
"debug",
"ocp",
"ocp",
`${p.name} Unpaused before the time allowed, time left ${
differenceInSeconds(parseInt(p.printDelay), timeBetween) //seconds
}`
);
unPausePrinter(p);
} else if (tmp[2] === "0" && timeBetween < parseInt(p.printDelay)) {
// was unpaused to soon so repause it
createLog(
"info",
"ocp",
"ocp",
`${p.name} Unpaused before the time allowed, time left ${
differenceInSeconds(parseInt(p.printDelay), timeBetween) //seconds
}`,
);
printerUpdate(p, 3);
pausePrinter(p);
} else if (tmp[2] === "0" && timeBetween > parseInt(p.printDelay)) {
// its been long enough we can print a label
createLog(
"debug",
"ocp",
"ocp",
`${p.name} Allowed time passed and printing new label`
);
printerUpdate(p, 3);
pausePrinter(p);
} else if (tmp[2] === "0" && timeBetween > parseInt(p.printDelay)) {
// its been long enough we can print a label
createLog(
"info",
"ocp",
"ocp",
`${p.name} Allowed time passed and printing new label`,
);
// update last time printed in the array
printerUpdate(p, 4);
// update last time printed in the array
printerUpdate(p, 4);
// sending over for labeling.
labelingProcess({ printer: p });
autoLabelCreated();
} else if (tmp[2] === "0") {
// printer was unpaused for the first time or made it here
createLog(
"debug",
"ocp",
"ocp",
`${p.name} Frist time printing`
);
// sending over for labeling.
labelingProcess({ printer: p });
autoLabelCreated();
} else if (tmp[2] === "0") {
// printer was unpaused for the first time or made it here
createLog("info", "ocp", "ocp", `${p.name} Frist time printing`);
// add the time and printer
printerUpdate(p, 4);
// add the time and printer
printerUpdate(p, 4);
// sending over for labeling.
labelingProcess({ printer: p });
autoLabelCreated();
} else if (tmp[2] === "1") {
// printer is paused and waiting
createLog(
"debug",
"ocp",
"ocp",
`${p.name} paused and waiting`
);
// sending over for labeling.
labelingProcess({ printer: p });
autoLabelCreated();
} else if (tmp[2] === "1") {
// printer is paused and waiting
createLog("debug", "ocp", "ocp", `${p.name} paused and waiting`);
printerUpdate(p, 6);
}
printerUpdate(p, 6);
}
printer.end();
printer.end();
resolve({ success: true, message: "Print cycle completed." });
});
resolve({ success: true, message: "Print cycle completed." });
});
// as a safety destory it if its still there
printer.on("end", () => {
setTimeout(() => {
if (!printer.destroyed) {
createLog(
"info",
"printerState",
"ocp",
`${p.name}: was force closed, during normal cycle counting`
);
printer.destroy();
}
}, 1000);
});
// as a safety destory it if its still there
printer.on("end", () => {
setTimeout(() => {
if (!printer.destroyed) {
createLog(
"info",
"printerState",
"ocp",
`${p.name}: was force closed, during normal cycle counting`,
);
printer.destroy();
}
}, 1000);
});
printer.on("error", async (error: any) => {
// just going to say theres an error with the printer
//console.log(error.code);
if (error.code.includes("ETIMEDOUT") && !errorCheck) {
createLog("error", "ocp", "ocp", `${p.name} is offline`);
printer.on("error", async (error: any) => {
// just going to say theres an error with the printer
//console.log(error.code);
if (error.code.includes("ETIMEDOUT") && !errorCheck) {
createLog("error", "ocp", "ocp", `${p.name} is offline`);
await printerUpdate(p, 9);
errorCheck = true;
printer.end();
await printerUpdate(p, 9);
errorCheck = true;
printer.end();
resolve({
success: false,
message: "The printer is offline.",
});
}
if (!error.code.includes("ETIMEDOUT") && !errorCheck) {
createLog(
"error",
"ocp",
"ocp",
`${p.name} encountered an error: ${error}`
);
resolve({
success: false,
message: "The printer is offline.",
});
}
if (!error.code.includes("ETIMEDOUT") && !errorCheck) {
createLog(
"error",
"ocp",
"ocp",
`${p.name} encountered an error: ${error}`,
);
await printerUpdate(p, 7);
errorCheck = true;
await printerUpdate(p, 7);
errorCheck = true;
// send log data
// fake line
printer.end();
resolve({
success: false,
message: "There was an error with the printer.",
});
}
});
});
// send log data
// fake line
printer.end();
resolve({
success: false,
message: "There was an error with the printer.",
});
}
});
});
};
export const autoLabelingStats = async (p: any) => {
/**
* Checks autolabeling printers just to see what they are doing.
*/
createLog("debug", "ocp", "ocp", `Printer cycling`);
/**
* Checks autolabeling printers just to see what they are doing.
*/
createLog("debug", "ocp", "ocp", `Printer cycling`);
const printer = new net.Socket();
const printer = new net.Socket();
return new Promise((resolve) => {
// connect to the printer, and check its status
printer.connect(p.port, p.ipAddress, async () => {
// write the message to the printer below gives us a feedback of the printer
printer.write("~HS");
});
return new Promise((resolve) => {
// connect to the printer, and check its status
printer.connect(p.port, p.ipAddress, async () => {
// write the message to the printer below gives us a feedback of the printer
printer.write("~HS");
});
// read the data from the printer
printer.on("data", async (data) => {
const res = data.toString();
// read the data from the printer
printer.on("data", async (data) => {
const res = data.toString();
// turn the data into an array to make it more easy to deal with
const tmp = res.split(",");
// turn the data into an array to make it more easy to deal with
const tmp = res.split(",");
if (tmp[4] !== "000") {
// unpaused and printing labels - reset timer
createLog("debug", "ocp", "ocp", `Printing Labels`);
if (tmp[4] !== "000") {
// unpaused and printing labels - reset timer
createLog("debug", "ocp", "ocp", `Printing Labels`);
// update last time printed in the array
printerUpdate(p, 1);
}
// update last time printed in the array
printerUpdate(p, 1);
}
if (tmp[4] === "000") {
// unpaused and printing labels - reset timer
createLog("debug", "ocp", "ocp", `Printing Labels`);
if (tmp[4] === "000") {
// unpaused and printing labels - reset timer
createLog("debug", "ocp", "ocp", `Printing Labels`);
// update last time printed in the array
printerUpdate(p, 5);
}
});
// update last time printed in the array
printerUpdate(p, 5);
}
});
printer.on("error", async (error) => {
// just going to say theres an error with the printer
console.log(error);
if (!errorCheck) {
createLog(
"error",
"ocp",
"ocp",
`${p.name}, encountered an error: ${error}`
);
}
printer.on("error", async (error) => {
// just going to say theres an error with the printer
console.log(error);
if (!errorCheck) {
createLog(
"error",
"ocp",
"ocp",
`${p.name}, encountered an error: ${error}`,
);
}
await printerUpdate(p, 7);
errorCheck = true;
await printerUpdate(p, 7);
errorCheck = true;
// send log data
// fake line
printer.end();
resolve({
success: false,
message: "There was an error with the printer.",
});
});
});
// send log data
// fake line
printer.end();
resolve({
success: false,
message: "There was an error with the printer.",
});
});
});
};

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "lst",
"version": "1.5.0",
"version": "1.6.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "lst",
"version": "1.5.0",
"version": "1.6.0",
"license": "ISC",
"dependencies": {
"@dotenvx/dotenvx": "^1.51.0",

View File

@@ -1,6 +1,6 @@
{
"name": "lst",
"version": "1.5.0",
"version": "1.6.0",
"description": "Logistics support tool - the place where the support happens.",
"main": "index.js",
"scripts": {