Compare commits
7 Commits
78f7b8a179
...
bb7931d6c8
| Author | SHA1 | Date | |
|---|---|---|---|
| bb7931d6c8 | |||
| 4f848bb649 | |||
| f8335f5217 | |||
| 2a35381fe4 | |||
| e6b92aeb10 | |||
| da87e2e1d3 | |||
| 3ef0f230dd |
0
backend/datamart/datamart.controller.test.ts
Normal file
0
backend/datamart/datamart.controller.test.ts
Normal file
68
backend/datamart/getDatamart.route.test.ts
Normal file
68
backend/datamart/getDatamart.route.test.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import express from "express";
|
||||
import request from "supertest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("../db/db.controller.js", () => ({
|
||||
db: {},
|
||||
}));
|
||||
|
||||
vi.mock("../logger/logger.controller.js", () => ({
|
||||
createLogger: () => ({
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("./datamart.controller.js", () => ({
|
||||
runDatamartQuery: vi.fn(async ({ name, options }) => ({
|
||||
success: true,
|
||||
message: `Ran ${name}`,
|
||||
data: {
|
||||
name,
|
||||
options,
|
||||
},
|
||||
})),
|
||||
}));
|
||||
|
||||
import { runDatamartQuery } from "./datamart.controller.js";
|
||||
import getDatamartRoute from "./getDatamart.route.js";
|
||||
|
||||
function createTestApp() {
|
||||
const app = express();
|
||||
|
||||
app.use(express.json());
|
||||
app.use("/datamart", getDatamartRoute);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
describe("GET /datamart/:name", () => {
|
||||
it("runs a datamart query by name and returns api response", async () => {
|
||||
const app = createTestApp();
|
||||
|
||||
const res = await request(app).get("/datamart/orders").query({
|
||||
value: "123",
|
||||
});
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
expect(runDatamartQuery).toHaveBeenCalledWith({
|
||||
name: "orders",
|
||||
options: {
|
||||
value: "123",
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.body.success).toBe(true);
|
||||
expect(res.body.module).toBe("datamart");
|
||||
expect(res.body.subModule).toBe("query");
|
||||
expect(res.body.data).toEqual({
|
||||
name: "orders",
|
||||
options: {
|
||||
value: "123",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,18 +1,70 @@
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { Router } from "express";
|
||||
import z from "zod";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { dockDoorScanners } from "../db/schema/dockdoor.schema.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
const endLoading = z.object({
|
||||
loadingOrder: z.string(),
|
||||
dockId: z.string(),
|
||||
});
|
||||
|
||||
r.post("/", async (req, res) => {
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "dockdoor",
|
||||
subModule: "lane check",
|
||||
message: `Release x is being closed now. the bol should come out at the default printer.`,
|
||||
data: req.body ?? [],
|
||||
status: 200,
|
||||
});
|
||||
// close the loading order
|
||||
|
||||
// clear the loading order off the dock
|
||||
|
||||
try {
|
||||
const validated = endLoading.parse(req.body);
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.update(dockDoorScanners)
|
||||
.set({
|
||||
currentLoadingOrder: "",
|
||||
upd_date: sql`NOW()`,
|
||||
upd_user: req.user?.username,
|
||||
})
|
||||
.where(eq(dockDoorScanners.dockId, validated.dockId))
|
||||
.returning(),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "dockdoor",
|
||||
subModule: "loadingOrder",
|
||||
message: `Failed to updating the dock.`,
|
||||
data: (error as any) ?? [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "dockdoor",
|
||||
subModule: "loadingOrder",
|
||||
message: `Loading order ${validated.loadingOrder} was just closed.`,
|
||||
data: data ?? [],
|
||||
status: 200,
|
||||
});
|
||||
} catch (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "dockdoor",
|
||||
subModule: "loadingOrder",
|
||||
message: `Failed to start loading order.`,
|
||||
data: (error as any) ?? [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default r;
|
||||
|
||||
25
backend/dockdoorScanning/dockdoor.loadUnits.route.ts
Normal file
25
backend/dockdoorScanning/dockdoor.loadUnits.route.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Router } from "express";
|
||||
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import loadUnit from "./dockdoor.loadUnits.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.post("/", async (req, res) => {
|
||||
const unit = await loadUnit({
|
||||
dockId: req.body.dockId,
|
||||
runningNo: req.body.runningNo,
|
||||
});
|
||||
|
||||
return apiReturn(res, {
|
||||
success: unit.success,
|
||||
level: "info",
|
||||
module: "dockdoor",
|
||||
subModule: "loadingUnit",
|
||||
message: unit.message,
|
||||
data: unit?.data ?? [],
|
||||
status: unit.success ? 200 : 400,
|
||||
});
|
||||
});
|
||||
|
||||
export default r;
|
||||
@@ -3,6 +3,7 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { dockDoorScanners } from "../db/schema/dockdoor.schema.js";
|
||||
import { emitToRoom } from "../socket.io/roomEmitter.socket.js";
|
||||
import { runProdApi } from "../utils/prodEndpoint.utils.js";
|
||||
import { returnFunc } from "../utils/returnHelper.utils.js";
|
||||
|
||||
@@ -11,10 +12,10 @@ import { returnFunc } from "../utils/returnHelper.utils.js";
|
||||
type Data = {
|
||||
dockId?: string;
|
||||
sscc?: string;
|
||||
runningNr?: string;
|
||||
runningNo?: string;
|
||||
};
|
||||
|
||||
export const loadUnit = async (data: Data) => {
|
||||
const loadUnit = async (data: Data) => {
|
||||
// are we even active at this time?
|
||||
const dockDoorActive = await db.query.settings.findFirst({
|
||||
where: (u, { eq }) => eq(u.name, "dockDoorScanning"),
|
||||
@@ -44,7 +45,7 @@ export const loadUnit = async (data: Data) => {
|
||||
"Failed to load the unit to the truck, there was no pallet read.",
|
||||
data: [],
|
||||
notify: false,
|
||||
room: `dockDoorLoading${data.dockId}`,
|
||||
room: `dockDoorLoading:${data.dockId}`,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -64,7 +65,7 @@ export const loadUnit = async (data: Data) => {
|
||||
"There are know current active loading orders please start one and try again.",
|
||||
data: [],
|
||||
notify: false,
|
||||
room: `dockDoorLoading${data.dockId}`,
|
||||
room: `dockDoorLoading:${data.dockId}`,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -76,14 +77,67 @@ export const loadUnit = async (data: Data) => {
|
||||
|
||||
// add the loading units
|
||||
try {
|
||||
const prod = await runProdApi({
|
||||
const unitToScan = data.sscc
|
||||
? { sscc: data.sscc?.slice(2) }
|
||||
: { runningNo: Number(data.runningNo) };
|
||||
|
||||
const prod = (await runProdApi({
|
||||
method: "post",
|
||||
endpoint: `/public/v1.0/OutboundDeliveries/LoadingOrders/${dock[0]?.currentLoadingOrder}/LoadUnit`,
|
||||
data: [{ sscc: data.sscc?.slice(2) }],
|
||||
});
|
||||
data: [unitToScan],
|
||||
})) as any;
|
||||
|
||||
console.log(prod?.data);
|
||||
//emitToRoom(`dockDoorLoading:${data.dockId}`, prod?.data ?? []);
|
||||
|
||||
if (!prod?.success) {
|
||||
emitToRoom(`dockDoorLoading:${data.dockId}`, prod?.data.errors[0]);
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "dockdoor",
|
||||
subModule: "loadUnit",
|
||||
message: `Unit encountered an error while loading`,
|
||||
data: prod?.data.errors[0] as any,
|
||||
notify: false,
|
||||
//room: `dockDoorLoading:${data.dockId}`,
|
||||
});
|
||||
} else {
|
||||
const emitData = {
|
||||
message: `The unit ${prod.data.message.messageParams.runningNo} was loaded.`,
|
||||
data: prod.data,
|
||||
code: 0,
|
||||
};
|
||||
emitToRoom(`dockDoorLoading:${data.dockId}`, emitData as any);
|
||||
return returnFunc({
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "dockdoor",
|
||||
subModule: "loadUnit",
|
||||
message: `Unit added to loading order`,
|
||||
data: [
|
||||
{
|
||||
message: `The unit ${prod.data.message.messageParams.runningNo} was loaded.`,
|
||||
data: prod.data,
|
||||
code: 0,
|
||||
},
|
||||
] as any,
|
||||
notify: false,
|
||||
//room: `dockDoorLoading:${data.dockId}`,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return returnFunc({
|
||||
success: true,
|
||||
level: "error",
|
||||
module: "dockdoor",
|
||||
subModule: "loadUnit",
|
||||
message: `Failed to load unit`,
|
||||
data: error as any,
|
||||
notify: false,
|
||||
room: `dockDoorLoading:${data.dockId}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default loadUnit;
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Express } from "express";
|
||||
import { featureCheck } from "../middleware/featureActive.middleware.js";
|
||||
import activeLoadingOrders from "./dockdoor.activeLoadingOrders.route.js";
|
||||
import closeLoadingOrder from "./dockdoor.closeLoadingOrder.route.js";
|
||||
import load from "./dockdoor.loadUnits.route.js";
|
||||
import startLoad from "./dockdoor.startLoad.route.js";
|
||||
import prodDocks from "./dockdoors.docks.route.js";
|
||||
import docks from "./dockdoors.route.js";
|
||||
@@ -17,7 +18,7 @@ export const setupDockDoorRoutes = (baseUrl: string, app: Express) => {
|
||||
);
|
||||
|
||||
app.use(
|
||||
`${baseUrl}/api/dockDoor/closeLoadingOrder`,
|
||||
`${baseUrl}/api/dockDoor/finishOrder`,
|
||||
featureCheck("dockDoorScanning"),
|
||||
closeLoadingOrder,
|
||||
);
|
||||
@@ -37,7 +38,11 @@ export const setupDockDoorRoutes = (baseUrl: string, app: Express) => {
|
||||
prodDocks,
|
||||
);
|
||||
|
||||
// TODO : add manual way to add pallets
|
||||
app.use(
|
||||
`${baseUrl}/api/dockDoor/loadUnit`,
|
||||
featureCheck("dockDoorScanning"),
|
||||
load,
|
||||
);
|
||||
|
||||
// all other system should be under /api/system/*
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { sql } from "drizzle-orm";
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { Router } from "express";
|
||||
import z from "zod";
|
||||
import { db } from "../db/db.controller.js";
|
||||
@@ -25,6 +25,7 @@ r.post("/", async (req, res) => {
|
||||
upd_date: sql`NOW()`,
|
||||
upd_user: req.user?.username,
|
||||
})
|
||||
.where(eq(dockDoorScanners.dockId, validated.dockId))
|
||||
.returning(),
|
||||
);
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ const dbStream = new Writable({
|
||||
subModule: obj?.subModule?.toLowerCase(),
|
||||
hostname: obj?.hostname?.toLowerCase(),
|
||||
message: obj.msg,
|
||||
stack: obj?.stack,
|
||||
stack: obj?.stack || obj?.error, // this will add in the error or stack depending on how we pass it.
|
||||
})
|
||||
.returning(),
|
||||
);
|
||||
|
||||
@@ -49,7 +49,7 @@ const historicalInvImport = async () => {
|
||||
});
|
||||
}
|
||||
|
||||
if (data?.length === 0) {
|
||||
if (data.length === 0) {
|
||||
const avSQLQuery = sqlQuerySelector(`datamart.activeArticles`) as SqlQuery;
|
||||
|
||||
if (!avSQLQuery.success) {
|
||||
@@ -139,7 +139,7 @@ const historicalInvImport = async () => {
|
||||
subModule: "inv",
|
||||
message: `Error adding historical data to lst db`,
|
||||
data: errorImport as any,
|
||||
notify: true,
|
||||
notify: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,14 @@ type Releases = {
|
||||
DeliveryAddressHumanReadableId: string;
|
||||
AdditionalInformation1: string;
|
||||
};
|
||||
|
||||
// TODO: add these docs into the db
|
||||
const actaulDocks = [
|
||||
{ name: "cermac", dockId: "bcb17fae-0b1a-47a7-9fbf-594c5ebccce9" },
|
||||
{ name: "matrix", dockId: "3e32cbfc-49f4-4138-b491-9d5df9c94754" },
|
||||
{ name: "gerber", dockId: "9109e789-6c15-4cd9-87cb-de1b18627b6d" },
|
||||
{ name: "rb", dockId: "6be02526-6183-4789-a73f-e0aa155e6d1e" },
|
||||
];
|
||||
const timeZone = process.env.TIMEZONE as string;
|
||||
const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
|
||||
const log = createLogger({ module: "opendock", subModule: "releaseMonitor" });
|
||||
@@ -64,6 +72,7 @@ let lastCheck = formatInTimeZone(
|
||||
// };
|
||||
|
||||
const postRelease = async (release: Releases) => {
|
||||
log.debug({}, `Release: ${release.ReleaseNumber} is about to be validated`);
|
||||
if (!odToken.odToken) {
|
||||
log.info({}, "Getting Auth Token");
|
||||
await getToken();
|
||||
@@ -82,13 +91,29 @@ const postRelease = async (release: Releases) => {
|
||||
where: (u, { eq }) => eq(u.name, "defaultLoadType"),
|
||||
});
|
||||
|
||||
// check if the release has the data in it
|
||||
// check if the release has the loadtype in it
|
||||
const releaseLoadtypeCheck = (release.AdditionalInformation1 ?? "")
|
||||
.toLowerCase()
|
||||
.split(",")
|
||||
.map((x) => x.trim())
|
||||
.includes("drop");
|
||||
|
||||
// allowed to schedule now, as long as we see od in here somewhere
|
||||
const releaseOkToSchedule = (release.AdditionalInformation1 ?? "")
|
||||
.toLowerCase()
|
||||
.split(",")
|
||||
.map((x) => x.trim())
|
||||
.includes("od");
|
||||
|
||||
// dock was sent over
|
||||
const releaseDockInfo = actaulDocks.some((dock) =>
|
||||
(release.AdditionalInformation1 ?? "")
|
||||
.toLowerCase()
|
||||
.split(",")
|
||||
.map((x) => x.trim())
|
||||
.includes(dock.name.toLowerCase()),
|
||||
);
|
||||
|
||||
const opendDockArticleCheck = await db.query.opendockArticleSetup.findFirst({
|
||||
where: (table, { and, eq }) =>
|
||||
and(
|
||||
@@ -97,6 +122,24 @@ const postRelease = async (release: Releases) => {
|
||||
),
|
||||
});
|
||||
|
||||
// selected dock
|
||||
const releaseDocks = (release.AdditionalInformation1 ?? "")
|
||||
.toLowerCase()
|
||||
.split(",")
|
||||
.map((x) => x.trim());
|
||||
|
||||
const matchedDock = actaulDocks.find((dock) =>
|
||||
releaseDocks.includes(dock.name.toLowerCase()),
|
||||
);
|
||||
|
||||
const setDock =
|
||||
// validate we dont have the dock in the release
|
||||
releaseDockInfo
|
||||
? matchedDock?.dockId
|
||||
: // validate we dont have the dock in the aritcle check
|
||||
(actaulDocks.find((d) => d.name === opendDockArticleCheck?.dock)
|
||||
?.dockId ?? process.env.DEFAULT_DOCK);
|
||||
|
||||
// TODO: add in docks from lst db here to make it more universal for the team
|
||||
/**
|
||||
* ReleaseState
|
||||
@@ -127,7 +170,7 @@ const postRelease = async (release: Releases) => {
|
||||
userId: process.env.DEFAULT_CARRIER, // this should be the carrierid
|
||||
loadTypeId: process.env.DEFAULT_LOAD_TYPE, // well get this and make it a default one
|
||||
// TODO: look in the remarks in the release and if its says
|
||||
dockId: process.env.DEFAULT_DOCK, // this the warehouse we want it in to start out
|
||||
dockId: setDock, // this the warehouse we want it in to start out
|
||||
refNumbers: [release.ReleaseNumber],
|
||||
//refNumber: release.ReleaseNumber,
|
||||
start: release.DeliveryDate,
|
||||
@@ -403,7 +446,12 @@ const postRelease = async (release: Releases) => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if (
|
||||
(releaseLoadtypeCheck ||
|
||||
opendDockArticleCheck?.loadType === "drop" ||
|
||||
defaultDock?.value === "drop") &&
|
||||
releaseOkToSchedule
|
||||
) {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${process.env.OPENDOCK_URL}/appointment`,
|
||||
@@ -458,6 +506,76 @@ const postRelease = async (release: Releases) => {
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// try {
|
||||
// const response = await axios.post(
|
||||
// `${process.env.OPENDOCK_URL}/appointment`,
|
||||
// newDockApt,
|
||||
// {
|
||||
// headers: {
|
||||
// "content-type": "application/json; charset=utf-8",
|
||||
// Authorization: `Bearer ${odToken.odToken}`,
|
||||
// },
|
||||
// },
|
||||
// );
|
||||
|
||||
// // we need the id,release#,status from this response, store it in lst, check if we have a release so we can just update it.
|
||||
// // this will be utilized when we are listening for the changes to the apts. that way we can update the state to arrived. we will run our own checks on this guy during the incoming messages.
|
||||
|
||||
// if (response.status === 400) {
|
||||
// log.error({}, response.data.data.message);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // the response to make it simple we want response.data.id, response.data.relNumber, status will be defaulted to Scheduled if we created it here.
|
||||
// // TODO: add this release data to our db. but save it in json format and well parse it out. that way we future proof it and have everything in here vs just a few things
|
||||
// //console.info(response.data.data, "Was Created");
|
||||
// try {
|
||||
// await db
|
||||
// .insert(opendockApt)
|
||||
// .values({
|
||||
// release: release.ReleaseNumber,
|
||||
// openDockAptId: response.data.data.id,
|
||||
// appointment: response.data.data,
|
||||
// })
|
||||
// .onConflictDoUpdate({
|
||||
// target: opendockApt.release,
|
||||
// set: {
|
||||
// openDockAptId: response.data.data.id,
|
||||
// appointment: response.data.data,
|
||||
// upd_date: sql`NOW()`,
|
||||
// },
|
||||
// })
|
||||
// .returning();
|
||||
|
||||
// log.info({}, `${release.ReleaseNumber} was created`);
|
||||
// } catch (e) {
|
||||
// log.error({ stack: e }, "Error creating new release");
|
||||
// }
|
||||
// // biome-ignore lint/suspicious/noExplicitAny: to many possibilities
|
||||
// } catch (e: any) {
|
||||
// log.error(
|
||||
// { stack: e?.response?.data },
|
||||
// `Error posting new release to opendock, ${release.ReleaseNumber}`,
|
||||
// );
|
||||
|
||||
// return;
|
||||
// }
|
||||
|
||||
log.info(
|
||||
{
|
||||
stack: {
|
||||
release: release.ReleaseNumber,
|
||||
releaseLoadtypeCheck,
|
||||
articleLoadType: opendDockArticleCheck?.loadType,
|
||||
defaultLoadType: defaultDock?.value,
|
||||
releaseOkToSchedule,
|
||||
},
|
||||
},
|
||||
`Skipping OpenDock post - release: ${release.ReleaseNumber} is not allowed to schedule`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await delay(750); // rate limit protection
|
||||
|
||||
@@ -1,11 +1,80 @@
|
||||
SELECT count(*) as activated
|
||||
FROM [test1_AlplaPROD2.0_Read].[support].[FeatureActivation]
|
||||
|
||||
where feature in (108,7)
|
||||
where feature in (7)
|
||||
|
||||
|
||||
/*
|
||||
as more features get activated and need to have this checked to include the new endpoints add here so we can check this.
|
||||
108 = waste
|
||||
7 = warehousing
|
||||
[DefaultTranslation("Blocking")]
|
||||
Blocking = 1,
|
||||
|
||||
[DefaultTranslation("Users")]
|
||||
UserManagement = 2,
|
||||
|
||||
[DefaultTranslation("Complaint Handling")]
|
||||
ComplaintHandling = 3,
|
||||
|
||||
[DefaultTranslation("Demand Management")]
|
||||
DemandManagement = 4,
|
||||
|
||||
[DefaultTranslation("Issue Material")]
|
||||
IssueMaterial = 5,
|
||||
|
||||
[DefaultTranslation("Production Controlling")]
|
||||
ProductionControlling = 6,
|
||||
|
||||
[DefaultTranslation("Warehousing")]
|
||||
Warehousing = 7,
|
||||
|
||||
[DefaultTranslation("Outbound Deliveries")]
|
||||
OutboundDeliveries = 8,
|
||||
|
||||
[DefaultTranslation("Production Scheduling")]
|
||||
ProductionScheduling = 9,
|
||||
|
||||
[DefaultTranslation("Advanced Scheduling")]
|
||||
AdvancedScheduling = 10,
|
||||
|
||||
[DefaultTranslation("Material Requirements Planning")]
|
||||
MaterialRequirementsPlanning = 11,
|
||||
|
||||
[DefaultTranslation("Production Labelling")]
|
||||
ProductionLabelling = 12,
|
||||
|
||||
[SpecialProcess]
|
||||
[DefaultTranslation("Accounting")]
|
||||
Accounting = 100,
|
||||
|
||||
[SpecialProcess]
|
||||
[DefaultTranslation("Irradiation")]
|
||||
Irradiation = 101,
|
||||
|
||||
[SpecialProcess]
|
||||
[DefaultTranslation("Central Moulds")]
|
||||
CentralMoulds = 102,
|
||||
|
||||
[SpecialProcess]
|
||||
[DefaultTranslation("Maintenance")]
|
||||
Maintenance = 103,
|
||||
|
||||
[SpecialProcess]
|
||||
[DefaultTranslation("Disable Manual Bookings")]
|
||||
DisableManualBookings = 104,
|
||||
|
||||
[SpecialProcess]
|
||||
[DefaultTranslation("Purchasing")]
|
||||
Purchasing = 105,
|
||||
|
||||
[SpecialProcess]
|
||||
[DefaultTranslation("Tracing")]
|
||||
Tracing = 106,
|
||||
|
||||
[SpecialProcess]
|
||||
[DefaultTranslation("AlplaERP (D365)")]
|
||||
AlplaErp = 107,
|
||||
|
||||
[SpecialProcess]
|
||||
[DefaultTranslation("AI chatbot")]
|
||||
AiChatBot = 108
|
||||
*/
|
||||
|
||||
@@ -7,7 +7,13 @@ type RoomDefinition<T = unknown> = {
|
||||
seed: (limit: number) => Promise<T[]>;
|
||||
};
|
||||
|
||||
export type StaticRoomId = "logs" | "labels" | "admin" | "admin:build" | "ppoo";
|
||||
export type StaticRoomId =
|
||||
| "logs"
|
||||
| "labels"
|
||||
| "admin"
|
||||
| "admin:build"
|
||||
| "ppoo"
|
||||
| "dockDoorLoading:2";
|
||||
export type DynamicRoomId = `dockDoorLoading:${string}`;
|
||||
export type RoomId = StaticRoomId | DynamicRoomId;
|
||||
|
||||
@@ -24,6 +30,7 @@ export const protectedRooms: Record<StaticRoomId, RoomConfig> = {
|
||||
admin: {},
|
||||
"admin:build": {},
|
||||
ppoo: {},
|
||||
"dockDoorLoading:2": {},
|
||||
};
|
||||
|
||||
export function getRoomConfig(roomId: string): RoomConfig | null {
|
||||
@@ -91,4 +98,12 @@ export const roomDefinition: Record<RoomId, RoomDefinition> = {
|
||||
} as any;
|
||||
},
|
||||
},
|
||||
|
||||
// TODO: add in dynamic room seeding
|
||||
"dockDoorLoading:2": {
|
||||
seed: async (limit) => {
|
||||
console.log(limit);
|
||||
return [];
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import { eq } from "drizzle-orm";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { dockDoorScanners } from "../db/schema/dockdoor.schema.js";
|
||||
import { printerData } from "../db/schema/printers.schema.js";
|
||||
import { loadUnit } from "../dockdoorScanning/dockdoor.loadUnits.js";
|
||||
import loadUnit from "../dockdoorScanning/dockdoor.loadUnits.js";
|
||||
import { createLogger } from "../logger/logger.controller.js";
|
||||
import { delay } from "../utils/delay.utils.js";
|
||||
import { returnFunc } from "../utils/returnHelper.utils.js";
|
||||
@@ -79,7 +79,6 @@ export const startTCPServer = async () => {
|
||||
}
|
||||
|
||||
// check if its a dock door scanner
|
||||
// TODO: move to the db and get real info lol
|
||||
const dockdoorScanners = await db.select().from(dockDoorScanners);
|
||||
|
||||
if (dockdoorScanners.some((s) => s.ip === ip.replace("::ffff:", ""))) {
|
||||
|
||||
@@ -13,7 +13,14 @@ import {
|
||||
//import { eq } from "drizzle-orm";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import * as rawSchema from "../db/schema/auth.schema.js";
|
||||
import { ac, admin, manager, systemAdmin, user } from "./auth.permissions.js";
|
||||
import {
|
||||
ac,
|
||||
admin,
|
||||
manager,
|
||||
systemAdmin,
|
||||
transport,
|
||||
user,
|
||||
} from "./auth.permissions.js";
|
||||
import { allowedOrigins } from "./cors.utils.js";
|
||||
import { sendEmail } from "./sendEmail.utils.js";
|
||||
|
||||
@@ -164,6 +171,7 @@ export const auth = betterAuth({
|
||||
admin,
|
||||
user,
|
||||
manager,
|
||||
transport,
|
||||
systemAdmin,
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -11,10 +11,12 @@ type RoomErrorPayload = {
|
||||
message?: string;
|
||||
};
|
||||
|
||||
type UpdateMode = "append" | "replace";
|
||||
|
||||
export function useSocketRoom<T>(
|
||||
roomId: string,
|
||||
enabled = true,
|
||||
getKey?: (item: T) => string | number,
|
||||
updateMode: UpdateMode = "append",
|
||||
) {
|
||||
const [data, setData] = useState<T[]>([]);
|
||||
const [info, setInfo] = useState(
|
||||
@@ -36,7 +38,6 @@ export function useSocketRoom<T>(
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!roomId || !enabled) return;
|
||||
function handleConnect() {
|
||||
socket.emit("join-room", roomId);
|
||||
setInfo(`Joined room: ${roomId}`);
|
||||
@@ -46,7 +47,13 @@ export function useSocketRoom<T>(
|
||||
// protects against other room updates hitting this hook
|
||||
if (payload.roomId !== roomId) return;
|
||||
|
||||
setData((prev) => [...payload.payloads, ...prev]);
|
||||
// resetting room data for rooms that just need updated data.
|
||||
if (updateMode === "replace") {
|
||||
setData(payload.payloads);
|
||||
} else {
|
||||
setData((prev) => [...payload.payloads, ...prev]);
|
||||
}
|
||||
|
||||
setInfo("");
|
||||
}
|
||||
|
||||
@@ -55,14 +62,14 @@ export function useSocketRoom<T>(
|
||||
setInfo(err.message ?? "Room error");
|
||||
}
|
||||
|
||||
if (!socket.connected) {
|
||||
socket.connect();
|
||||
}
|
||||
|
||||
socket.on("connect", handleConnect);
|
||||
socket.on("room-update", handleUpdate);
|
||||
socket.on("room-error", handleError);
|
||||
|
||||
if (!socket.connected && socket.disconnected) {
|
||||
socket.connect();
|
||||
}
|
||||
|
||||
// If already connected, join immediately
|
||||
if (socket.connected) {
|
||||
socket.emit("join-room", roomId);
|
||||
@@ -76,7 +83,7 @@ export function useSocketRoom<T>(
|
||||
socket.off("room-update", handleUpdate);
|
||||
socket.off("room-error", handleError);
|
||||
};
|
||||
}, [roomId, enabled]);
|
||||
}, [roomId, updateMode]);
|
||||
|
||||
return { data, info, clearRoom };
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import z from "zod";
|
||||
|
||||
import { useSession } from "../lib/auth-client";
|
||||
import { trackLstEvent } from "../lib/umami.utils";
|
||||
|
||||
|
||||
@@ -132,6 +132,10 @@ export default function NewArticleLink({ refetch }: { refetch: any }) {
|
||||
label: "Matrix",
|
||||
value: "matrix",
|
||||
},
|
||||
{
|
||||
label: "RB",
|
||||
value: "rb",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
"foregroundImage": "./assets/adaptive-icon-white.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"versionCode": 39,
|
||||
"versionCode": 42,
|
||||
"minSupportedVersionCode": 33,
|
||||
"predictiveBackGestureEnabled": false,
|
||||
"package": "net.alpla.lst.mobile",
|
||||
"permissions": [
|
||||
"android.permission.WRITE_EXTERNAL_STORAGE",
|
||||
"android.permission.READ_EXTERNAL_STORAGE"
|
||||
]
|
||||
"android.permission.WRITE_EXTERNAL_STORAGE",
|
||||
"android.permission.READ_EXTERNAL_STORAGE"
|
||||
]
|
||||
},
|
||||
"web": {
|
||||
"output": "static",
|
||||
|
||||
16
lstMobile/package-lock.json
generated
16
lstMobile/package-lock.json
generated
@@ -16,7 +16,7 @@
|
||||
"@rn-primitives/portal": "^1.4.0",
|
||||
"@rn-primitives/separator": "^1.4.0",
|
||||
"@rn-primitives/slot": "^1.4.0",
|
||||
"@tanstack/react-query": "^5.99.0",
|
||||
"@tanstack/react-query": "^5.100.14",
|
||||
"axios": "^1.15.0",
|
||||
"babel-preset-expo": "^55.0.18",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
@@ -5318,9 +5318,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tanstack/query-core": {
|
||||
"version": "5.100.9",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.9.tgz",
|
||||
"integrity": "sha512-SJSFw1S8+kQ0+knv/XGfrbocWoAlT7vDKsSImtLx3ZPQmEcR46hkDjLSvynSy25N8Ms4tIEini1FuBd5k7IscQ==",
|
||||
"version": "5.100.14",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.14.tgz",
|
||||
"integrity": "sha512-5X41dGpxgeaHISCRW2oYwcSycZeULZzAunaudXT9ov1KOTj9xwt0CH6hbwqP1/z74ZWF7rYFnDpyYH07XFcZew==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -5328,12 +5328,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-query": {
|
||||
"version": "5.100.9",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.9.tgz",
|
||||
"integrity": "sha512-Oa44XkaI3kCNN6ME0KByU3xT3SEUNOMfZpHxL6+wFoTm+OeUFYHKdeYVe0aOXlRDm/f15sgLwEt2HDorIdW8+A==",
|
||||
"version": "5.100.14",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.14.tgz",
|
||||
"integrity": "sha512-oOr6aRdSFEwWhzxEkD/9ZcItM3+LjBSkeVmadWKwUssAHTsqd/7bOjWrX4AbvEkoEhgAxzN0Xk6H/aYzXiYBAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "5.100.9"
|
||||
"@tanstack/query-core": "5.100.14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"@rn-primitives/portal": "^1.4.0",
|
||||
"@rn-primitives/separator": "^1.4.0",
|
||||
"@rn-primitives/slot": "^1.4.0",
|
||||
"@tanstack/react-query": "^5.99.0",
|
||||
"@tanstack/react-query": "^5.100.14",
|
||||
"axios": "^1.15.0",
|
||||
"babel-preset-expo": "^55.0.18",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
|
||||
@@ -51,7 +51,7 @@ export default function TabsLayout() {
|
||||
onPress: async () => {
|
||||
// clear auth/session
|
||||
logoutScanner();
|
||||
router.replace("/(tabs)/scanner");
|
||||
router.replace("/");
|
||||
|
||||
// clear zustand/session stuff
|
||||
//useAuthStore.getState().reset();
|
||||
|
||||
@@ -1,26 +1,134 @@
|
||||
import React from "react";
|
||||
import { Text, View } from "react-native";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import * as Device from "expo-device";
|
||||
import { Link } from "expo-router";
|
||||
import {
|
||||
Button,
|
||||
ScrollView,
|
||||
Text,
|
||||
useWindowDimensions,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function LaneCheck() {
|
||||
const getInfo = async () => {
|
||||
const info = "ho";
|
||||
import { Card, CardContent } from "../../components/ui/card";
|
||||
import { getActiveLoadingOrders } from "../../lib/queryStuff/getActiveLoadingOrders";
|
||||
import { getDocks } from "../../lib/queryStuff/getDocks";
|
||||
|
||||
console.log(info);
|
||||
export default function DockScan() {
|
||||
const { data } = useSuspenseQuery(getDocks());
|
||||
const {
|
||||
data: loadingOrders,
|
||||
refetch,
|
||||
isLoading,
|
||||
} = useSuspenseQuery(getActiveLoadingOrders());
|
||||
const { width } = useWindowDimensions();
|
||||
const isTablet =
|
||||
Device.modelName?.toLowerCase().includes("et40") ||
|
||||
Device.modelName?.toLowerCase().includes("et45");
|
||||
|
||||
const columns = isTablet ? 3 : 1;
|
||||
|
||||
const gap = 8;
|
||||
const cardWidth =
|
||||
columns === 1 ? width - 16 : (width - gap * (columns + 1)) / columns;
|
||||
|
||||
const updateLoadingOrders = () => {
|
||||
refetch();
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: `Refreshing Loading Orders`,
|
||||
});
|
||||
};
|
||||
|
||||
if (isLoading)
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<Text>Loading...</Text>
|
||||
</SafeAreaView>
|
||||
);
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
//justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginTop: 50,
|
||||
}}
|
||||
>
|
||||
<Text>Dock Scanning</Text>
|
||||
<Button onPress={getInfo}>
|
||||
<Text>Check info</Text>
|
||||
</Button>
|
||||
<View className="flex">
|
||||
<View
|
||||
style={{
|
||||
// flex: 1,
|
||||
//justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginTop: 50,
|
||||
}}
|
||||
>
|
||||
<Text className="text-2xl text-bold">Dock Scanning</Text>
|
||||
<Button title="Update Loading Orders" onPress={updateLoadingOrders} />
|
||||
</View>
|
||||
<View>
|
||||
<SafeAreaView className="flex">
|
||||
<ScrollView className="w-full">
|
||||
<View className="w-full flex-row flex-wrap gap-2 m-2">
|
||||
{data.map((i: any) => {
|
||||
const loadingPlan =
|
||||
i.currentLoadingOrder !== ""
|
||||
? loadingOrders.filter(
|
||||
(x: any) => x.id === Number(i.currentLoadingOrder),
|
||||
)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<View key={i.id}>
|
||||
<Link
|
||||
href={{
|
||||
pathname: "/dock/[id]",
|
||||
params: {
|
||||
id: i.dockId.toString(),
|
||||
currentLoading: i.currentLoadingOrder,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
style={{
|
||||
borderWidth: 4,
|
||||
width: cardWidth,
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<Text>{i.name}</Text>
|
||||
{i.currentLoadingOrder === "" ? (
|
||||
<Text>Tap to active new loading order</Text>
|
||||
) : (
|
||||
<View>
|
||||
<Text>
|
||||
Current Loading order : {i.currentLoadingOrder}
|
||||
</Text>
|
||||
{loadingPlan && loadingPlan.length > 0 && (
|
||||
<View>
|
||||
<Text>
|
||||
{`${loadingPlan[0].loadingPlanItems[0].articleId} - ${loadingPlan[0].loadingPlanItems[0].articleDescription}`}
|
||||
</Text>
|
||||
<Text>
|
||||
Current Loaded :{" "}
|
||||
{
|
||||
loadingPlan[0].loadingPlanItems[0]
|
||||
.loadedQuantityLUs
|
||||
}{" "}
|
||||
/{" "}
|
||||
{
|
||||
loadingPlan[0].loadingPlanItems[0]
|
||||
.plannedQuantityLUs
|
||||
}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,8 +44,6 @@ export default function PPOO() {
|
||||
});
|
||||
}, [items, sortDir]);
|
||||
|
||||
//console.log(logsInfo);
|
||||
|
||||
return (
|
||||
<View className="flex items-center mt-2">
|
||||
<View className="flex m-2">
|
||||
@@ -61,7 +59,7 @@ export default function PPOO() {
|
||||
<Text>Loading PPOO...</Text>
|
||||
</View>
|
||||
) : (
|
||||
<SafeAreaView className="flex-1">
|
||||
<SafeAreaView className="flex">
|
||||
<ScrollView className="w-full">
|
||||
<View className="w-full flex-row flex-wrap gap-2 m-2">
|
||||
{sortedItems.map((i: any) => {
|
||||
|
||||
@@ -2,14 +2,18 @@ import { PortalHost } from "@rn-primitives/portal";
|
||||
import { Stack } from "expo-router";
|
||||
import { StatusBar } from "expo-status-bar";
|
||||
import "../../global.css";
|
||||
import { QueryClientProvider } from "@tanstack/react-query";
|
||||
import { useEffect } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
import useDeviceLock from "../hooks/useDeviceCheck";
|
||||
import { useDeviceOrientationLock } from "../hooks/useDeviceOrientationLock";
|
||||
import { queryClient } from "../lib/queryStuff/queryClient";
|
||||
import { connectSocket } from "../lib/socket.io";
|
||||
import { zebraScanner } from "../lib/ZebraScanner";
|
||||
|
||||
export default function RootLayout() {
|
||||
useDeviceLock();
|
||||
useDeviceOrientationLock();
|
||||
|
||||
useEffect(() => {
|
||||
zebraScanner.ensureProfile();
|
||||
@@ -18,15 +22,18 @@ export default function RootLayout() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<StatusBar style="dark" />
|
||||
<Stack screenOptions={{ headerShown: false }}>
|
||||
<Stack.Screen name="index" />
|
||||
<Stack.Screen name="login" />
|
||||
<Stack.Screen name="setup" />
|
||||
<Stack.Screen name="updateScreen" />
|
||||
<Stack.Screen name="(tabs)" />
|
||||
</Stack>
|
||||
<PortalHost />
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<StatusBar style="dark" />
|
||||
<Stack screenOptions={{ headerShown: false }}>
|
||||
<Stack.Screen name="index" />
|
||||
<Stack.Screen name="login" />
|
||||
<Stack.Screen name="setup" />
|
||||
<Stack.Screen name="updateScreen" />
|
||||
<Stack.Screen name="(tabs)" />
|
||||
</Stack>
|
||||
<PortalHost />
|
||||
</QueryClientProvider>
|
||||
|
||||
<Toast />
|
||||
</>
|
||||
);
|
||||
|
||||
169
lstMobile/src/app/dock/[id].tsx
Normal file
169
lstMobile/src/app/dock/[id].tsx
Normal file
@@ -0,0 +1,169 @@
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import * as Device from "expo-device";
|
||||
import { useLocalSearchParams, useRouter } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Pressable,
|
||||
ScrollView,
|
||||
Text,
|
||||
useWindowDimensions,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { Card, CardContent } from "../../components/ui/card";
|
||||
import { api } from "../../lib/apiHelper";
|
||||
import { getActiveLoadingOrders } from "../../lib/queryStuff/getActiveLoadingOrders";
|
||||
|
||||
export default function DockPage() {
|
||||
const { id, currentLoading } = useLocalSearchParams<{
|
||||
id: string;
|
||||
currentLoading: string;
|
||||
}>();
|
||||
const router = useRouter();
|
||||
const [active] = useState(currentLoading !== "");
|
||||
|
||||
const { width } = useWindowDimensions();
|
||||
const isTablet =
|
||||
Device.modelName?.toLowerCase().includes("et40") ||
|
||||
Device.modelName?.toLowerCase().includes("et45");
|
||||
|
||||
const columns = isTablet ? 3 : 1;
|
||||
|
||||
const gap = 8;
|
||||
const cardWidth =
|
||||
columns === 1 ? width - 16 : (width - gap * (columns + 1)) / columns;
|
||||
|
||||
const {
|
||||
data: loadingOrders,
|
||||
refetch,
|
||||
isLoading,
|
||||
} = useSuspenseQuery(getActiveLoadingOrders());
|
||||
|
||||
const dockFilter = loadingOrders.filter((i: any) => i.dockId === Number(id));
|
||||
|
||||
// add in start loading order, if this is already on the dock we will disabled and change to view current pallets
|
||||
const startLoad = async (loadingOrder: string, dockId: string) => {
|
||||
try {
|
||||
const res = await api.post("/dockDoor/startLoad", {
|
||||
loadingOrder,
|
||||
dockId,
|
||||
});
|
||||
|
||||
if (res.status === 200) {
|
||||
Toast.show({ type: "success", text1: res.data.message });
|
||||
refetch();
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: JSON.stringify(error),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const endLoad = async (loadingOrder: string, dockId: string) => {
|
||||
try {
|
||||
const res = await api.post("/dockDoor/endLoad", {
|
||||
loadingOrder,
|
||||
dockId,
|
||||
});
|
||||
|
||||
if (res.status === 200) {
|
||||
Toast.show({ type: "success", text1: res.data.message });
|
||||
refetch();
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: JSON.stringify(error),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// add in ending loading order disabeled until all pallets are loaded.
|
||||
if (isLoading)
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<Text>Loading</Text>
|
||||
</SafeAreaView>
|
||||
);
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<View className="flex flex-row justify-between gap-1 ml-1 mr-1">
|
||||
<View>
|
||||
<Pressable
|
||||
onPress={() => router.back()}
|
||||
className="self-start rounded-xl bg-gray-200 px-4 py-2"
|
||||
>
|
||||
<Text className="font-semibold">← Back</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<Text className="text-xl mt-1">{dockFilter[0].dockDescription}</Text>
|
||||
<View>
|
||||
<Pressable
|
||||
onPress={() =>
|
||||
router.push({
|
||||
pathname: "/dock/scans/[scanner]",
|
||||
params: {
|
||||
scanner: id,
|
||||
},
|
||||
})
|
||||
}
|
||||
className="self-start rounded-xl bg-gray-200 px-4 py-2"
|
||||
>
|
||||
<Text className="font-semibold">Scans</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
<ScrollView>
|
||||
<View className="w-full flex-row flex-wrap gap-2 m-2">
|
||||
{dockFilter.map((i: any) => {
|
||||
return (
|
||||
<View key={i.id}>
|
||||
<Card
|
||||
style={{
|
||||
borderWidth: 4,
|
||||
width: cardWidth,
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<View>
|
||||
<Text>Loading Order: {dockFilter[0].id}</Text>
|
||||
<Text>
|
||||
{`${dockFilter[0].loadingPlanItems[0].articleId} - ${dockFilter[0].loadingPlanItems[0].articleDescription}`}
|
||||
</Text>
|
||||
<Text>
|
||||
Current Loaded :{" "}
|
||||
{dockFilter[0].loadingPlanItems[0].loadedQuantityLUs} /{" "}
|
||||
{dockFilter[0].loadingPlanItems[0].plannedQuantityLUs}
|
||||
</Text>
|
||||
</View>
|
||||
<View className="mt-2 flex flex-row gap-2 justify-between">
|
||||
<Button
|
||||
title="Start Load"
|
||||
onPress={() =>
|
||||
startLoad(dockFilter[0].id.toString(), id)
|
||||
}
|
||||
disabled={active}
|
||||
/>
|
||||
<Button
|
||||
title="End Load"
|
||||
onPress={() => endLoad(dockFilter[0].id.toString(), id)}
|
||||
disabled={active}
|
||||
/>
|
||||
</View>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
71
lstMobile/src/app/dock/scans/[scanner].tsx
Normal file
71
lstMobile/src/app/dock/scans/[scanner].tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { useLocalSearchParams, useRouter } from "expo-router";
|
||||
import { Pressable, Text, View } from "react-native";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import { Card } from "../../../components/ui/card";
|
||||
import { useSocketRoom } from "../../../hooks/socket.io.hook";
|
||||
import { getActiveLoadingOrders } from "../../../lib/queryStuff/getActiveLoadingOrders";
|
||||
|
||||
export default function DockPage() {
|
||||
const { scanner } = useLocalSearchParams<{
|
||||
scanner: string;
|
||||
}>();
|
||||
const { data: loadingOrders, isLoading } = useSuspenseQuery(
|
||||
getActiveLoadingOrders(),
|
||||
);
|
||||
const { data } = useSocketRoom<any>(
|
||||
`dockDoorLoading:${scanner}`,
|
||||
undefined,
|
||||
"append",
|
||||
) as any;
|
||||
const dockFilter = loadingOrders.filter(
|
||||
(i: any) => i.dockId === Number(scanner),
|
||||
);
|
||||
const router = useRouter();
|
||||
|
||||
if (isLoading)
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<Text>Loading...</Text>
|
||||
</SafeAreaView>
|
||||
);
|
||||
return (
|
||||
<SafeAreaView className="w-full">
|
||||
<View className="flex flex-row justify-between gap-1 ml-1 mr-1">
|
||||
<View>
|
||||
<Pressable
|
||||
onPress={() => router.back()}
|
||||
className="self-start rounded-xl bg-gray-200 px-4 py-2"
|
||||
>
|
||||
<Text className="font-semibold">← Back</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<Text className="text-xl mt-1">{dockFilter[0].dockDescription}</Text>
|
||||
<View>
|
||||
<Pressable
|
||||
onPress={() =>
|
||||
router.replace({
|
||||
pathname: "/(tabs)/dockScan",
|
||||
})
|
||||
}
|
||||
className="self-start rounded-xl bg-gray-200 px-4 py-2"
|
||||
>
|
||||
<Text className="font-semibold">Docks</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
<View>
|
||||
{data.map((i: any, index: any) => {
|
||||
return (
|
||||
<View key={index} className="m-2">
|
||||
<Card>
|
||||
<Text>{JSON.stringify(i)}</Text>
|
||||
</Card>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import axios from "axios";
|
||||
|
||||
import { useRouter } from "expo-router";
|
||||
import { Settings } from "lucide-react-native";
|
||||
import { useState } from "react";
|
||||
import { Alert, Button, Text, View } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { ConfigButton } from "../components/ui/configButton";
|
||||
import { Input } from "../components/ui/input";
|
||||
import { useAppStore } from "../hooks/useAppStore";
|
||||
import { useMobileAuthStore } from "../hooks/useMobileAuth";
|
||||
@@ -52,11 +54,6 @@ export default function Login() {
|
||||
}
|
||||
};
|
||||
|
||||
const config = () => {
|
||||
console.log("config");
|
||||
return router.replace("/setup");
|
||||
};
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
@@ -67,9 +64,17 @@ export default function Login() {
|
||||
}}
|
||||
>
|
||||
<View className="flex items-center m-5">
|
||||
<Text style={{ fontSize: 20, fontWeight: "600" }}>
|
||||
LST Scanner Login
|
||||
</Text>
|
||||
<View className="flex flex-row">
|
||||
<View>
|
||||
<Text style={{ fontSize: 20, fontWeight: "600" }} className="mt-2">
|
||||
LST Scanner Login
|
||||
</Text>
|
||||
</View>
|
||||
<View>
|
||||
<ConfigButton />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className="w-64 p-4">
|
||||
<Input
|
||||
className="w-fit"
|
||||
@@ -89,7 +94,6 @@ export default function Login() {
|
||||
</View>
|
||||
<View className="flex gap-2 flex-row">
|
||||
<Button title="Login" onPress={onLogin} />
|
||||
<Button title="Config" onPress={config} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
20
lstMobile/src/components/ui/configButton.tsx
Normal file
20
lstMobile/src/components/ui/configButton.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { useRouter } from "expo-router";
|
||||
import { Settings } from "lucide-react-native";
|
||||
import { Pressable } from "react-native";
|
||||
|
||||
export function ConfigButton() {
|
||||
const router = useRouter();
|
||||
const config = () => {
|
||||
console.log("config");
|
||||
return router.replace("/setup");
|
||||
};
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
onPress={config}
|
||||
className="h-12 w-12 items-center justify-center rounded-x"
|
||||
>
|
||||
<Settings color="black" size={24} />
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import axios from "axios";
|
||||
import Constants from "expo-constants";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { setApiConfig } from "../lib/apiHelper";
|
||||
import { devDelay } from "../lib/devMode";
|
||||
import { versionCheck } from "../lib/versionValidation";
|
||||
import { useAppStore } from "./useAppStore";
|
||||
@@ -26,6 +27,11 @@ export function useAppStartup() {
|
||||
const serverPort = useAppStore((s) => s.serverPort);
|
||||
const serverIp = useAppStore((s) => s.serverIp);
|
||||
|
||||
setApiConfig({
|
||||
serverIp,
|
||||
serverPort,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasHydrated) {
|
||||
setStatus("loading");
|
||||
|
||||
32
lstMobile/src/hooks/useDeviceOrientationLock.ts
Normal file
32
lstMobile/src/hooks/useDeviceOrientationLock.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import * as Device from "expo-device";
|
||||
import * as ScreenOrientation from "expo-screen-orientation";
|
||||
import { useEffect } from "react";
|
||||
|
||||
const LANDSCAPE_MODELS = ["ET45", "ET40"]; // tablets
|
||||
const PORTRAIT_MODELS = ["TC21", "TC26", "TC8300"]; // scanners
|
||||
|
||||
const isTabletModel = (modelName?: string | null) => {
|
||||
const model = modelName?.toUpperCase() ?? "";
|
||||
|
||||
return LANDSCAPE_MODELS.some((m) => model.includes(m));
|
||||
};
|
||||
|
||||
export function useDeviceOrientationLock() {
|
||||
useEffect(() => {
|
||||
async function lockOrientation() {
|
||||
try {
|
||||
const model = Device.modelName;
|
||||
|
||||
await ScreenOrientation.lockAsync(
|
||||
isTabletModel(model)
|
||||
? ScreenOrientation.OrientationLock.LANDSCAPE
|
||||
: ScreenOrientation.OrientationLock.PORTRAIT_UP,
|
||||
);
|
||||
} catch (err) {
|
||||
console.warn("Failed to lock orientation", err);
|
||||
}
|
||||
}
|
||||
|
||||
void lockOrientation();
|
||||
}, []);
|
||||
}
|
||||
63
lstMobile/src/lib/apiHelper.ts
Normal file
63
lstMobile/src/lib/apiHelper.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import axios from "axios";
|
||||
import { router } from "expo-router";
|
||||
|
||||
type ApiConfig = {
|
||||
serverIp: string;
|
||||
serverPort: string | number;
|
||||
};
|
||||
|
||||
let currentConfig: ApiConfig | null = null;
|
||||
|
||||
export function setApiConfig(config: ApiConfig) {
|
||||
currentConfig = config;
|
||||
}
|
||||
|
||||
function getBaseUrl() {
|
||||
if (!currentConfig) {
|
||||
throw new Error("API config not initialized");
|
||||
}
|
||||
console.log(
|
||||
`http://${currentConfig.serverIp}:${currentConfig.serverPort}/lst/api`,
|
||||
);
|
||||
return `http://${currentConfig.serverIp}:${currentConfig.serverPort}/lst/api`;
|
||||
}
|
||||
|
||||
export const api = axios.create({
|
||||
timeout: 15000,
|
||||
});
|
||||
|
||||
api.interceptors.request.use((config) => {
|
||||
config.baseURL = getBaseUrl();
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
api.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
const isNetworkError =
|
||||
error.code === "ERR_NETWORK" ||
|
||||
error.code === "ECONNABORTED" ||
|
||||
error.message === "Network Error" ||
|
||||
error.message === "Failed to fetch" ||
|
||||
!error.response;
|
||||
|
||||
// unauthorized
|
||||
if (error.response?.status === 401) {
|
||||
router.replace("/login");
|
||||
}
|
||||
|
||||
// forbidden
|
||||
if (error.response?.status === 403) {
|
||||
router.replace("/");
|
||||
}
|
||||
|
||||
// app/server offline
|
||||
if (isNetworkError) {
|
||||
router.replace("/");
|
||||
}
|
||||
|
||||
console.log(error);
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
21
lstMobile/src/lib/queryStuff/getActiveLoadingOrders.ts
Normal file
21
lstMobile/src/lib/queryStuff/getActiveLoadingOrders.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
||||
import { api } from "../apiHelper";
|
||||
|
||||
export function getActiveLoadingOrders() {
|
||||
return queryOptions({
|
||||
queryKey: ["getActiveLoadingOrders"],
|
||||
queryFn: () => dataFetch(),
|
||||
staleTime: 5000,
|
||||
refetchOnWindowFocus: true,
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
}
|
||||
|
||||
const dataFetch = async () => {
|
||||
const { data } = await api.get("/dockDoor/activeLoadingOrders");
|
||||
if (!data.success) {
|
||||
throw new Error(data.message ?? "Failed to load articles");
|
||||
}
|
||||
|
||||
return data.data ?? [];
|
||||
};
|
||||
21
lstMobile/src/lib/queryStuff/getDocks.ts
Normal file
21
lstMobile/src/lib/queryStuff/getDocks.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
||||
import { api } from "../apiHelper";
|
||||
|
||||
export function getDocks() {
|
||||
return queryOptions({
|
||||
queryKey: ["getDocks"],
|
||||
queryFn: () => dataFetch(),
|
||||
staleTime: 5000,
|
||||
refetchOnWindowFocus: true,
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
}
|
||||
|
||||
const dataFetch = async () => {
|
||||
const { data } = await api.get("/dockDoor/scanners");
|
||||
if (!data.success) {
|
||||
throw new Error(data.message ?? "Failed to load articles");
|
||||
}
|
||||
|
||||
return data.data ?? [];
|
||||
};
|
||||
17
lstMobile/src/lib/queryStuff/queryClient.ts
Normal file
17
lstMobile/src/lib/queryStuff/queryClient.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { QueryClient } from "@tanstack/react-query";
|
||||
|
||||
export const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 30_000,
|
||||
gcTime: 5 * 60_000,
|
||||
retry: 2,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: true,
|
||||
refetchOnMount: false,
|
||||
},
|
||||
mutations: {
|
||||
retry: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -65,7 +65,7 @@ export const versionCheck = async () => {
|
||||
const build = Constants.expoConfig?.android?.versionCode ?? 1;
|
||||
|
||||
if (build < res.data.versionCode) {
|
||||
await downloadLatestApk(serverIp, port);
|
||||
//await downloadLatestApk(serverIp, port);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Version check error:", error);
|
||||
|
||||
1366
package-lock.json
generated
1366
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "dotenvx run -f .env -- vitest",
|
||||
"test:run": "dotenvx run -f .env -- vitest run",
|
||||
"dev": "concurrently -n \"server,frontend\" -c \"#007755, #1F73D1\" \"npm run dev:app\" \"npm run dev:frontend\"",
|
||||
"dev:app": "dotenvx run -f .env -- tsx watch backend/server.ts",
|
||||
"dev:frontend": "cd frontend && npm run dev",
|
||||
@@ -59,9 +60,11 @@
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"npm-check-updates": "^19.6.5",
|
||||
"openapi-types": "^12.1.3",
|
||||
"supertest": "^7.2.2",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5.9.3"
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.1.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dotenvx/dotenvx": "^1.57.0",
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
// import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
// import { connectProdSql } from "../backend/src/prodSql/prodSqlConnection.controller.js";
|
||||
|
||||
// let pool: unknown;
|
||||
|
||||
// describe("Prod SQL connection", () => {
|
||||
// // This may take seconds, so give plenty of time
|
||||
// vi.setTimeout(30000);
|
||||
|
||||
// beforeAll(async () => {
|
||||
// pool = await connectProdSql();
|
||||
// });
|
||||
|
||||
// afterAll(async () => {
|
||||
// if (pool?.close) await pool.close();
|
||||
// });
|
||||
|
||||
// it("should connect and return expected data", async () => {
|
||||
// // Example query — use something safe and consistent
|
||||
// const result = await pool
|
||||
// .request()
|
||||
// .query("SELECT TOP 1 id, name FROM Users ORDER BY id ASC");
|
||||
|
||||
// expect(result.recordset).toBeDefined();
|
||||
// expect(Array.isArray(result.recordset)).toBe(true);
|
||||
// expect(result.recordset.length).toBeGreaterThan(0);
|
||||
// expect(result.recordset[0]).toHaveProperty("id");
|
||||
// });
|
||||
// });
|
||||
@@ -1,42 +0,0 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
import { sendEmail } from "../backend/src/utils/sendEmail.utils";
|
||||
|
||||
// Mock the logger before imports
|
||||
vi.mock("../backend/src/logger/logger.controller", () => ({
|
||||
createLogger: () => ({
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
fatal: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("Mail sending", () => {
|
||||
test("should send an email successfully", async () => {
|
||||
const result = await sendEmail({
|
||||
email: "blake.matthes@alpla.com",
|
||||
subject: "LST - Testing system",
|
||||
template: "testEmail",
|
||||
context: { name: "blake" },
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({
|
||||
success: true,
|
||||
});
|
||||
|
||||
expect(result?.message).toContain("blake.matthes@alpla.com");
|
||||
});
|
||||
|
||||
test("should handle email send failure gracefully", async () => {
|
||||
const badResult = await sendEmail({
|
||||
email: "invalid-address",
|
||||
subject: "LST - Testing system",
|
||||
template: "",
|
||||
context: {},
|
||||
});
|
||||
|
||||
expect(badResult).toMatchObject({
|
||||
success: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,6 @@ import { defineConfig } from "vitest/config";
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
include: ["tests/**/*.test.ts"], // ← point to your tests folder
|
||||
include: ["backend/**/*.test.ts"], // ← point to your tests folder
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user