diff --git a/server/services/logistics/controller/warehouse/cycleCountChecks/cyclecountCheck.ts b/server/services/logistics/controller/warehouse/cycleCountChecks/cyclecountCheck.ts new file mode 100644 index 0000000..30a91d6 --- /dev/null +++ b/server/services/logistics/controller/warehouse/cycleCountChecks/cyclecountCheck.ts @@ -0,0 +1,59 @@ +import { differenceInDays, differenceInSeconds, format } from "date-fns"; +import { timeZoneFix } from "../../../../../globalUtils/timeZoneFix.js"; +import { createLog } from "../../../../logger/logger.js"; +import { delay } from "../../../../../globalUtils/delay.js"; +import { tryCatch } from "../../../../../globalUtils/tryCatch.js"; +import { query } from "../../../../sqlServer/prodSqlServer.js"; +import { cycleCountCheck } from "../../../../sqlServer/querys/warehouse/cycleCountCheck.js"; + +// setting timer for updating stockCheck on a restart will always check. +let lastCheck = 0; + +export const lanes: any = []; + +export const getLanesToCycleCount = async () => { + const currentTime: any = timeZoneFix(); + // store the lanes in memeory + createLog("info", "warehouse", "logistics", "Empty lane triggered update."); + lastCheck = currentTime; + const ageQuery = cycleCountCheck.replaceAll("[ageOfRow]", `1000`); + const { data: prodLanes, error: pl } = await tryCatch( + query(ageQuery, "Get Stock lane date.") + ); + + // run the update on the lanes + for (let i = 0; i < prodLanes.length; i++) { + const createLane = { + laneID: prodLanes[i]?.laneID, + warehouseID: prodLanes[i]?.warehouseID, + warehouseName: prodLanes[i]?.warehouseName || "na", + Description: prodLanes[i]?.Description, + LastMoveDate: prodLanes[i]?.LastMoveDate + ? format(prodLanes[i]?.LastInv, "M/d/yyyy") + : undefined, + LastInv: format(prodLanes[i]?.LastInv, "M/d/yyyy"), + rowType: prodLanes[i].rowType, + DaysSinceLast: + differenceInDays( + new Date(prodLanes[i].LastInv), + new Date(prodLanes[i].LastMoveDate) + ) <= 0 + ? 0 + : differenceInDays( + new Date(prodLanes[i].LastInv), + new Date(prodLanes[i].LastMoveDate) + ), + upd_date: format(new Date(Date.now()), "M/d/yyyy"), + }; + + lanes.push(createLane); + createLog( + "debug", + "warehouse", + "logistics", + `${lanes[i].Description} was just added` + ); + await delay(10); + //delay to slow this thing down + } +}; diff --git a/server/services/logistics/controller/warehouse/cycleCountChecks/getCycleCountCheck.ts b/server/services/logistics/controller/warehouse/cycleCountChecks/getCycleCountCheck.ts new file mode 100644 index 0000000..c3a9ba6 --- /dev/null +++ b/server/services/logistics/controller/warehouse/cycleCountChecks/getCycleCountCheck.ts @@ -0,0 +1,28 @@ +import { lanes } from "./cyclecountCheck.js"; + +export const getCycleCountCheck = async ( + age: number = 1000, + type: string = "" +) => { + /** + * Get the lane data based on the age and type + */ + + let filteredLanes = lanes.filter((t: any) => t.DaysSinceLast >= age); + + if (type != "") { + return { + sucess: true, + message: `${filteredLanes.length} lanes that are of type ${type} and have not been cycle counted in the last ${age} days.`, + data: filteredLanes.filter( + (t: any) => t.rowType === type.toUpperCase() + ), + }; + } else { + return { + success: true, + message: `${filteredLanes.length} lanes grabed that have not been cycle counted in the last ${age} days.`, + data: filteredLanes, + }; + } +}; diff --git a/server/services/logistics/route/getCycleCountChecks.ts b/server/services/logistics/route/getCycleCountChecks.ts new file mode 100644 index 0000000..ac53ea2 --- /dev/null +++ b/server/services/logistics/route/getCycleCountChecks.ts @@ -0,0 +1,60 @@ +import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi"; +import { responses } from "../../../globalUtils/routeDefs/responses.js"; +import { tryCatch } from "../../../globalUtils/tryCatch.js"; +import { getCycleCountCheck } from "../controller/warehouse/cycleCountChecks/getCycleCountCheck.js"; + +const app = new OpenAPIHono(); + +const Body = z + .object({ + age: z.number().optional().openapi({ example: 90 }), + //email: z.string().optional().openapi({example: "s.smith@example.com"}), + type: z.string().optional().openapi({ example: "fg" }), + }) + .openapi("User"); +app.openapi( + createRoute({ + tags: ["logistics"], + summary: "Returns lanes that need cycle counted", + method: "post", + path: "/cyclecountcheck", + request: { + body: { + content: { + "application/json": { schema: Body }, + }, + }, + }, + // description: + // "Provided a running number and lot number you can consume material.", + responses: responses(), + }), + async (c: any) => { + //apiHit(c, { endpoint: "api/sqlProd/close" }); + const { data: body, error: be } = await tryCatch(c.req.json()); + + if (be) { + return c.json({ success: false, message: "Missing Data." }); + } + + const check: any = body; + const { data: lanes, error: le } = await tryCatch( + getCycleCountCheck(check.age, check.type) + ); + + if (le) { + return c.json({ + success: false, + message: "Error getting lane data.", + data: le, + }); + } + + return c.json({ + success: lanes.success, + message: lanes.message, + data: lanes.data, + }); + } +); +export default app; diff --git a/server/services/ocp/controller/labeling/createLabel.ts b/server/services/ocp/controller/labeling/createLabel.ts index 079ddc2..84825f0 100644 --- a/server/services/ocp/controller/labeling/createLabel.ts +++ b/server/services/ocp/controller/labeling/createLabel.ts @@ -32,7 +32,7 @@ export const createLabel = async (data: any, userPrinted: any) => { if (settingsError) { return { success: false, - message: "There was an error getting the printer.", + message: "There was an error getting the settings.", settingsError, }; } diff --git a/server/services/ocp/controller/printers/printerCycleAutoLabelers.ts b/server/services/ocp/controller/printers/printerCycleAutoLabelers.ts new file mode 100644 index 0000000..e74e610 --- /dev/null +++ b/server/services/ocp/controller/printers/printerCycleAutoLabelers.ts @@ -0,0 +1,121 @@ +import { eq } from "drizzle-orm"; +import { db } from "../../../../../database/dbclient.js"; +import { settings } from "../../../../../database/schema/settings.js"; +import { tryCatch } from "../../../../globalUtils/tryCatch.js"; +import { createLog } from "../../../logger/logger.js"; +import { getPrinters } from "./getPrinters.js"; +import { autoLabelingStats } from "./printerStatus.js"; + +let isPrinterCycling = false; +let actualPrinterCycle: any; + +export const printerCycleAutoLabelers = async () => { + /** + * Will only check the auto labelers for status updates. + */ + + if (isPrinterCycling) + return { + success: false, + message: "Printers are already being cycled.", + }; + + createLog("info", "ocp", "ocp", "AutoLabeling cycle has started."); + // get the printers + const { data: s, error: se } = await tryCatch( + db.select().from(settings).where(eq(settings.name, "ocpCycleDelay")) + ); + if (se) { + createLog( + "error", + "ocp", + "ocp", + "There was an error getting the ocpCycleDelay." + ); + return { + success: false, + message: "Error getting printers.", + }; + } + + const ocpDelay: any = s; + isPrinterCycling = true; + // start the actual printer cycle + actualPrinterCycle = setInterval(async () => { + const { data, error } = await tryCatch(getPrinters()); + + if (error) { + createLog( + "error", + "ocp", + "ocp", + "There was an error getting the printers." + ); + return { + success: false, + message: "Error getting printers.", + }; + } + let printers: any = data.data; + + // only keep the assigned ones + printers = printers.filter((p: any) => p.assigned === true); + + // for printers we want to ignore there must be a remark stateing to ignore. + printers = printers.filter((p: any) => !p.remark.includes("ignore")); + + printers.forEach(async (p: any) => { + /** + * if the last timeprinted would be greater than x well just change the status to idle and extended based on the 2 times. + * + * to get a printer going again label will need to come from the front end as that will just unpause the printer and start the labeling, or the api for manual print + * well need to adjust this to actually print the label then unpause it. + * + * it will be + * + * less than x since time printed run the printer status + * greater than x but less than y change the status to idle, but ping to make sure its online, + * if greater than y change to extended idle but stil also ping to make sure its online. + */ + + // ignore pdf printer as we want it here for testing purposes + if (p.name.toLowerCase() === "pdf24") { + return; + } + + if (p.name === "Autolabeler") { + await autoLabelingStats(p); + return; + } + }); + }, parseInt(ocpDelay[0]?.value) * 2 * 1000); + + return { success: true, message: "AutoLabeling cycle has been started." }; +}; + +export const stopPrinterCycle = async () => { + /** + * We will stop the print cylce this is more an emergancy thing. + */ + if (actualPrinterCycle && !actualPrinterCycle._destroyed) { + createLog("info", "ocp", "ocp", "AutoLabeling cycle is being stopped."); + clearInterval(actualPrinterCycle); + isPrinterCycling = false; + return { + success: true, + message: "AutoLabeling cycle has been stopped.", + }; + } else { + createLog( + "info", + "ocp", + "ocp", + "AutoLabeling cycle is already stopped." + ); + isPrinterCycling = false; + return { + success: true, + message: "AutoLabeling cycle is already Stopped.", + }; + } +}; diff --git a/server/services/sqlServer/querys/warehouse/cycleCountCheck.ts b/server/services/sqlServer/querys/warehouse/cycleCountCheck.ts new file mode 100644 index 0000000..a3de95c --- /dev/null +++ b/server/services/sqlServer/querys/warehouse/cycleCountCheck.ts @@ -0,0 +1,80 @@ +export const cycleCountCheck = ` +-- Define the structure of the result set from the stored procedure +DECLARE @results TABLE ( +IdLocation INT, +LastMoveDate Date + +) +-- insert into the temp table +insert into @results +select IdLagerAbteilung, MAX(CaSE WHEN CONVERT(char(10), Buchungsdatum, 120) IS NULL THEN '1900-01-01' ELSE CONVERT(char(10), Buchungsdatum, 120) END) AS LastLocMov +from AlplaPROD_test1.dbo.V_LagerBuchungen x(nolock) + +group by IdLagerAbteilung + +select * from ( +select x.IdLagerAbteilung as laneID, +x.IdWarenLager as warehouseID, +w.Bezeichnung as warehouseName, +w.LagerTyp as warehouseIDTyp, +w.Standort as warehouseLocation, +x.Bezeichnung as Description, +LastMoveDate, +CASE WHEN CONVERT(char(10), i.Datum, 120) is null then getdate() - 365 else CONVERT(char(10), i.Datum, 120) end as LastInv, +--create the types of warehouses to choose from +case +-- empty +when (sum(EinlagerungsMengeSum) is null and Datum < LastMoveDate) or ( +(sum(EinlagerungsMengeSum) is null and Datum < DATEADD(day, -[ageOfRow], getdate())) +) then 'EMPTY' +-- finished goods +when w.LagerTyp = 2 and w.Standort = 10 then 'FG' +-- external finished goods +when w.LagerTyp = 2 and w.Standort = 20 then 'EXTERNAL' +-- silos +when w.LagerTyp in (3) and x.MaterialSilo = 1 then 'BULK' +-- MATERIALS +when w.LagerTyp = 3 and x.MaterialSilo = 0 then 'MATERIALS' + +-- MATERIALS +when w.LagerTyp = 11 then 'WASTE' +-- MATERIALS +when w.LagerTyp = 9 then 'PACKAGING' + +end as rowType, +CASE WHEN DateDiff(DAY,i.Datum,getDate()) is null then 1000 else DateDiff(DAY,i.Datum,getDate()) end as DaysSinceLast + +from AlplaPROD_test1.dbo.T_LagerAbteilungen as x (NOLOCK) + +-- last move +left join +@results as b on +x.IdLagerAbteilung = b.IdLocation + +-- last inv +left join +(select * from [AlplaPROD_test1].[dbo].[T_LagerAbteilungenInventuren] (nolock)) as i on x.IdLagerAbteilung = +i.IdLagerAbteilung + +-- useing this to determin only if the lane is empty +left join +(select * from [AlplaPROD_test1].dbo.V_LagerPositionenBarcodes (nolock)) as y on x.IdLagerAbteilung = +y.IdLagerAbteilung + +-- get the warehosue type +left join +(select * from [AlplaPROD_test1].dbo.T_WarenLager (nolock)) as w on x.IdWarenLager = w.IdWarenLager + +where x.aktiv = 1 and x.IdWarenLager not in (1,5,6) + +group by x.IdLagerAbteilung, +x.IdWarenLager, +w.LagerTyp, +w.Standort, +x.Bezeichnung, +LastMoveDate, +i.Datum, +x.MaterialSilo, +w.Bezeichnung +) xb +`;