feat(ocp): materials contorls and transfer to next lot logic

This commit is contained in:
2025-08-19 16:00:49 -05:00
parent a183279268
commit 88f61c8eaa
11 changed files with 997 additions and 53 deletions

View File

@@ -170,20 +170,15 @@ export const labelingProcess = async ({
};
}
// check mm is good
// check the material... mm,color (auto and manual combined), pkg
const mmStaged = await isMainMatStaged(filteredLot[0]);
if (!mmStaged) {
createLog(
"error",
"labeling",
"ocp",
`Main material is not prepaired for lot ${filteredLot[0].lot}`
);
if (!mmStaged.success) {
createLog("error", "labeling", "ocp", mmStaged.message);
return {
success: false,
message: `Main material is not prepaired for lot ${filteredLot[0].lot}`,
message: mmStaged.message,
};
}

View File

@@ -0,0 +1,206 @@
import { eq } from "drizzle-orm";
import { db } from "../../../../../database/dbclient.js";
import { printerData } from "../../../../../database/schema/printers.js";
import { runProdApi } from "../../../../globalUtils/runProdApi.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { createLog } from "../../../logger/logger.js";
import { query } from "../../../sqlServer/prodSqlServer.js";
import { labelInfo } from "../../../sqlServer/querys/warehouse/labelInfo.js";
type NewLotData = {
runnungNumber: number;
lotNumber: number;
originalAmount: number;
level: number;
};
/**
* Move manual material to a new lot.
*
* The data sent over should be
* Running number
* Lot number
* Orignal Quantity
* level of gaylord
*/
export const lotMaterialTransfer = async (data: NewLotData) => {
// get the barcode, and layoutID from the running number
const { data: label, error: labelError } = (await tryCatch(
query(
labelInfo.replace("[runningNr]", `${data.runnungNumber}`),
"Get label info"
)
)) as any;
if (labelError) {
createLog(
"error",
"materials",
"ocp",
"There was an error getting the label info"
);
return {
success: false,
message: "There was an error getting the label info",
data: labelError,
};
}
// if (
// label.data[0]?.stockStatus === "notOnStock" ||
// label.data.length === 0
// ) {
// createLog(
// "error",
// "materials",
// "ocp",
// `${data.runnungNumber}: dose not exist or no longer in stock.`
// );
// return {
// success: false,
// message: `${data.runnungNumber}: dose not exist or no longer in stock.`,
// data: [],
// };
// }
if (label.data[0]?.stockStatus === "onStock") {
createLog(
"error",
"materials",
"ocp",
`${data.runnungNumber}: currently in stock and not consumed to a lot.`
);
return {
success: false,
message: `${data.runnungNumber}: currently in stock and not consumed to a lot.`,
data: [],
};
}
// get the pdf24 printer id
const { data: printer, error: printerError } = (await tryCatch(
db.select().from(printerData).where(eq(printerData.name, "PDF24"))
)) as any;
if (printerError) {
createLog(
"error",
"materials",
"ocp",
"There was an error the printer info"
);
return {
success: false,
message: "There was an error the printer info",
data: printerError,
};
}
// calculate the remaining amount bascially it will be orignal number * level sent over
// level should be sent in a decimal .25 .5 .75 .95 the 95 will allow basically the what looks to be a full gaylord but we always want to consume something
const newQty = (data.originalAmount * data.level).toFixed(0);
// reprint the label and send it to pdf24
const reprintData = {
clientId: 999,
runningNo: label?.data[0].runnungNumber,
printerId: printer[0].humanReadableId,
layoutId: label?.data[0].labelLayout,
noOfCopies: 0,
quantity: newQty,
} as any;
const { data: reprint, error: reprintError } = (await tryCatch(
runProdApi({
endpoint: "/public/v1.0/ProductionLabelling/ReprintLabel",
data: [reprintData],
})
)) as any;
if (!reprint.success) {
createLog(
"error",
"materials",
"ocp",
`RN:${data.runnungNumber}, Error: ${reprint.data.data.message}`
);
return {
success: false,
message: `RN:${data.runnungNumber}, Error: ${reprint.data.data.message}`,
data: reprint,
};
}
// return the label back to fm1 lane id 10001
const matReturnData = {
barcode: label?.data[0].Barcode,
laneId: 10001,
};
const { data: matReturn, error: matReturError } = (await tryCatch(
runProdApi({
endpoint:
"/public/v1.0/IssueMaterial/ReturnPartiallyConsumedManualMaterial",
data: [matReturnData],
})
)) as any;
if (!matReturn.success) {
createLog(
"error",
"materials",
"ocp",
`RN:${data.runnungNumber}, Error ${matReturn.data.data.errors[0].message}`
);
return {
success: false,
message: `RN:${data.runnungNumber}, Error ${matReturn.data.data.errors[0].message}`,
data: matReturn,
};
}
// consume to the lot provided.
const consumeLot = {
productionLot: data.lotNumber,
barcode: label?.data[0].Barcode,
};
const { data: matConsume, error: matConsumeError } = (await tryCatch(
runProdApi({
endpoint:
"/public/v1.0/IssueMaterial/ConsumeNonPreparedManualMaterial",
data: [consumeLot],
})
)) as any;
if (!matConsume.success) {
createLog(
"error",
"materials",
"ocp",
`RN:${data.runnungNumber}, Error ${matConsume.data.data.errors[0].message}`
);
return {
success: false,
message: `RN:${data.runnungNumber}, Error ${matConsume.data.data.errors[0].message}`,
data: matConsume,
};
}
createLog(
"info",
"materials",
"ocp",
`RN:${data.runnungNumber}: qty: ${newQty}, was transfered to lot:${data.lotNumber}`
);
return {
success: true,
message: `RN:${data.runnungNumber}: qty: ${newQty}, was transfered to lot:${data.lotNumber}`,
data: [],
};
};
// setTimeout(async () => {
// lotMaterialTransfer({
// runnungNumber: 603468,
// lotNumber: 24897,
// originalAmount: 380,
// level: 0.95,
// });
// }, 5000);

View File

@@ -1,12 +1,14 @@
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { createLog } from "../../../logger/logger.js";
import { serverSettings } from "../../../server/controller/settings/getSettings.js";
import { query } from "../../../sqlServer/prodSqlServer.js";
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 = false;
let isStaged = { message: "Material is staged", success: true };
const { data, error } = (await tryCatch(
query(
@@ -26,7 +28,10 @@ export const isMainMatStaged = async (lot: any) => {
"ocp",
`The machine dose not require mm to print and book in.`
);
return true;
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
@@ -51,20 +56,101 @@ export const isMainMatStaged = async (lot: any) => {
const res: any = r.data;
createLog(
"info",
"mainMaterial",
"ocp",
`MainMaterial results: ${JSON.stringify(res)}`
);
if (res[0].Staged >= 1) {
isStaged = true;
}
// if (res[0].noShortage === "good") {
// if (res[0].Staged >= 1) {
// isStaged = true;
// }
createLog("info", "mainMaterial", "ocp", `Maint material query ran.`);
const mainMateiral = res.filter((n: any) => n.IsMainMaterial);
if (mainMateiral[0]?.noMaterialShortage === "no") {
isStaged = {
message: `Main material: ${mainMateiral[0].MaterialHumanReadableId} - ${mainMateiral[0].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");
if (checkColorSetting[0].value === "1") {
const autoConsumeColor = res.filter(
(n: any) => !n.IsMainMaterial && !n.isManual
);
if (
autoConsumeColor.some(
(n: any) => n.autoConsumeCheck === "autoConsumeNOK"
)
) {
const onlyNOK = autoConsumeColor.filter(
(n: any) => n.autoConsumeCheck === "autoConsumeNOK"
);
isStaged = {
message: `lot: ${lot.lot}, is missing: ${onlyNOK
.map(
(o: any) =>
`${o.MaterialHumanReadableId} - ${o.MaterialDescription}`
)
.join(",\n ")} for autoconsume`,
success: false,
};
}
// // for manual consume color
const manualConsumeColor = res.filter(
(n: any) => !n.IsMainMaterial && n.isManual
);
if (
manualConsumeColor.some(
(n: any) => n.noMaterialShortage === "yes"
)
) {
isStaged = {
message: `lot: ${lot.lot}, is missing: ${manualConsumeColor
.map(
(o: any) =>
`${o.MaterialHumanReadableId} - ${o.MaterialDescription}`
)
.join(",\n ")} for manual Material`,
success: false,
};
}
} 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 packagingCheck = res.filter(
(n: any) => !n.IsMainMaterial && n.isManual
);
if (packagingCheck.some((n: any) => n.noPKGShortage === "noPkg")) {
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",

View File

@@ -24,6 +24,7 @@ import { deleteLabels } from "../../globalUtils/dbCleanUp/labelCleanUp.js";
import bookInLabel from "./routes/labeling/bookIn.js";
import labelRatio from "./routes/labeling/getLabelRatio.js";
import resetRatio from "./routes/labeling/resetLabelRatio.js";
import materialTransferLot from "./routes/materials/lotTransfer.js";
const app = new OpenAPIHono();
@@ -47,6 +48,8 @@ const routes = [
//dyco
dycoCon,
dycoClose,
// materials
materialTransferLot,
] as const;
const setting = await db.select().from(settings);
@@ -101,4 +104,5 @@ setInterval(() => {
setInterval(() => {
updatePrinters();
}, 1000 * 60 * 60 * 24);
export default app;

View File

@@ -0,0 +1,64 @@
// an external way to creating logs
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
import { responses } from "../../../../globalUtils/routeDefs/responses.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { apiHit } from "../../../../globalUtils/apiHits.js";
import { lotMaterialTransfer } from "../../controller/materials/lotTransfer.js";
const app = new OpenAPIHono({ strict: false });
const LotTransfer = z.object({
runnungNumber: z.number().openapi({ example: 1234 }),
lotNumber: z.number().openapi({ example: 1235 }),
originalAmount: z.number().openapi({ example: 457 }),
level: z.number().openapi({ examples: [0.24, 0.5, 0.75, 0.95] }),
});
app.openapi(
createRoute({
tags: ["ocp"],
summary: "Transfers a gaylord of material to provided lot",
method: "post",
path: "/materiallottransfer",
request: {
body: { content: { "application/json": { schema: LotTransfer } } },
},
responses: responses(),
}),
async (c) => {
//const hours = c.req.query("hours");
const { data: bodyData, error: bodyError } = await tryCatch(
c.req.json()
);
apiHit(c, { endpoint: "/materiallottransfer", lastBody: bodyData });
if (bodyError) {
return c.json({
success: false,
message: "You are missing data",
});
}
const { data: transferMaterial, error: transferError } = await tryCatch(
lotMaterialTransfer(bodyData)
);
if (transferError) {
console.log(transferError);
return c.json({
success: false,
message:
"There was an error transfering the material to the next lot.",
data: transferError,
});
}
return c.json({
success: transferMaterial.success,
message: transferMaterial.message,
data: transferMaterial.data,
});
}
);
export default app;

View File

@@ -245,6 +245,20 @@ const newSettings = [
roleToChange: "admin",
},
// temp settings can be deleted at a later date once that code is removed
{
name: "checkColor",
value: `0`,
description: "Checks autoconsume and manual consume color",
serviceBelowsTo: "admin",
roleToChange: "admin",
},
{
name: "checkPKG",
value: `0`,
description: "Checks checks if we have enough packaging or not",
serviceBelowsTo: "admin",
roleToChange: "admin",
},
{
name: "siloAdjMigrations",
value: `0`,

View File

@@ -1,22 +1,117 @@
// export const mmQuery = `
// SELECT lot.ProductionLotHumanReadableId,
// case when SourcingState in (1, 2) then 1
// when x.ProvidedAmount > 0 then 1
// when x.EffectiveConsumption > 0 then 1
// else 0 end as Staged,
// x.ProvidedAmount as Provided,
// x.EffectiveConsumption as consumption,
// x.TotalDemand as totalNeeded,
// /* remaining needed to complete the lot */
// x.TotalDemand - x.EffectiveConsumption as remainingNeeded,
// /* do we have enough staged or scanned to the lot? */
// case when (case when x.ProvidedAmount <> 0 then x.ProvidedAmount else x.EffectiveConsumption end) > x.TotalDemand then 'good' else 'bad' end as noShortage ,
// x.IsManualProcess as isManual,
// MaterialHumanReadableId
// FROM [test1_AlplaPROD2.0_Read].[issueMaterial].[MaterialDemand] x (nolock)
// left join
// [test1_AlplaPROD2.0_Read].[issueMaterial].[ProductionLot] as lot on
// x.ProductionLotId = lot.Id
// where lot.ProductionLotHumanReadableId = [lotNumber]
// and IsMainMaterial = 1
// `;
export const mmQuery = `
SELECT lot.ProductionLotHumanReadableId,
case when SourcingState in (1, 2) then 1
when x.ProvidedAmount > 0 then 1
when x.EffectiveConsumption > 0 then 1
else 0 end as Staged,
x.ProvidedAmount as Provided,
x.EffectiveConsumption as consumption,
x.TotalDemand as totalNeeded,
use [test1_AlplaPROD2.0_Read]
/*
checks all needed material including pkg
we only want to monitor the manual materials and the mm materail to make sure they are good.
for auto consume materails this will be compared with another query.
*/
SELECT lot.ProductionLotHumanReadableId
,MaterialHumanReadableId
,MaterialDescription
--IsMainMaterial,
--case when SourcingState in (1, 2) then 1
--when x.ProvidedAmount > 0 then 1
--when x.EffectiveConsumption > 0 then 1
--else 0 end as Staged,
,x.ProvidedAmount as Provided
,x.EffectiveConsumption as consumption
,x.TotalDemand as totalDemand ,
case when cp.Pieces = 1 then (lot.TotalProducedLoadingUnits+1) * p.LoadingUnitPieces else
(a.Weight *(cp.Percentage / 100) * ((lot.TotalProducedLoadingUnits+1) * p.LoadingUnitPieces)) / 1000 end totalNeeded
,l.qty as invForAutoConsume
/* remaining needed to complete the lot */
x.TotalDemand - x.EffectiveConsumption as remainingNeeded,
--,x.TotalDemand - x.EffectiveConsumption as remainingNeeded
/* do we have enough staged or scanned to the lot? */
case when (case when x.ProvidedAmount <> 0 then x.ProvidedAmount else x.EffectiveConsumption end) > x.TotalDemand then 'good' else 'bad' end as noShortage ,
x.IsManualProcess as isManual,
MaterialHumanReadableId
FROM [test1_AlplaPROD2.0_Read].[issueMaterial].[MaterialDemand] x (nolock)
,case when (case when x.ProvidedAmount <> 0
then x.ProvidedAmount else x.EffectiveConsumption end) >
case when cp.Pieces = 1 then (lot.TotalProducedLoadingUnits+1) * p.LoadingUnitPieces else
(a.Weight *(cp.Percentage / 100) * ((lot.TotalProducedLoadingUnits+1) * p.LoadingUnitPieces)) / 1000 end
then 'good'
else 'no' end as noMaterialShortage
-- pkg check
,case when pkg.QuantityPosition is null then null else
(case when l.qty > (lot.TotalProducedLoadingUnits+1) * pkg.QuantityPosition then 'pkgGood' else 'noPkg' end) end as noPKGShortage
,case when cp.Percentage is null then
case when l.qty > ((lot.TotalProducedLoadingUnits +1) * pkg.QuantityPosition) then 'autoConsumeOk' else 'autoConsumeNOK' end else
(case when l.qty > (a.Weight *(cp.Percentage / 100) * ((lot.TotalProducedLoadingUnits+1) * p.LoadingUnitPieces)) / 1000 and IsManualProcess = 0
then 'autoConsumeOk' else 'autoConsumeNOK' end) end as autoConsumeCheck
,x.IsManualProcess as isManual
,IsMainMaterial
,lot.TotalPlannedLoadingUnits
,lot.TotalProducedLoadingUnits
,lot.TotalPlannedLoadingUnits - lot.TotalProducedLoadingUnits as remainingPallets
,cp.Percentage
,case when cp.Pieces is not null then cp.Pieces else pkg.QuantityPosition end as peices
FROM [issueMaterial].[MaterialDemand] x (nolock)
left join
[test1_AlplaPROD2.0_Read].[issueMaterial].[ProductionLot] as lot on
x.ProductionLotId = lot.Id
[productionControlling].[ProducedLot] as lot on
lot.Id = x.ProductionLotId
/* av data */
left join
[masterData].[Article] as a on
a.id = lot.ArticleId
/* compound*/
left join
[masterData].[CompoundPosition] as cp on
cp.CompoundId = a.CompoundId
and cp.ArticleId = x.MaterialId
/* packagaing */
left join
[masterData].[PackagingInstructionPosition] as pkg on
pkg.PackagingInstructionId = lot.PackagingId
and pkg.ArticleId = x.MaterialId
-- get the pkg stuff so we have the total amount per pallet.
left join
[masterData].[PackagingInstruction] as p on
p.id = lot.PackagingId
/* current stock info for auto consumption*/
left join
(select
IdArtikelVarianten
,ArtikelVariantenBez
,sum(VerfuegbareMengeSum) as qty
from AlplaPROD_test1.dbo.V_LagerPositionenBarcodes as i (nolock)
left join
AlplaPROD_test1.dbo.V_LagerAbteilungen as l (nolock) on
l.IdLagerAbteilung = i.IdLagerAbteilung
where autoverbrauch = 1 and aktiv = 1
group by IdArtikelVarianten,ArtikelVariantenBez
) as l on
l.IdArtikelVarianten = MaterialHumanReadableId
where lot.ProductionLotHumanReadableId = [lotNumber]
and IsMainMaterial = 1
`;

View File

@@ -1,20 +1,36 @@
export const labelInfo = `
select top(500) e.Barcode,
e.IdArtikelvarianten as av,
e.lfdnr as rn,
IdEtikettenLayout as labelLayout,
AnzStkJePalette,
Sonstiges_9,AnzStkJeKarton
,case when EinlagerungsMengeSum IS NULL then 'notOnStock' else 'onStock' end as stockStatus
,EinlagerungsMengeSum
--,*
from [AlplaPROD_test1].dbo.[T_EtikettenGedruckt] e (nolock)
declare @runningNumber nvarchar(max) = [runningNr]
select e.Barcode,
e.RunningNumber as runnungNumber,
externalRunningNumber= null,
e.ArticleHumanReadableId as av,
e.LabelManagementHumanReadableId as labelLayout,
case when EinlagerungsMengeSum IS NULL then 'notOnStock' else 'onStock' end as stockStatus
from [test1_AlplaPROD2.0_Read].[labelling].[InternalLabel] (nolock) as e
left join
[AlplaPROD_test1].dbo.V_LagerPositionenBarcodes as l on
e.LfdNr = l.Lfdnr
alplaprod_test1.dbo.V_LagerPositionenBarcodes (nolock) as l on
e.RunningNumber = l.Lfdnr
where e.LfdNr in ([runningNr])
WHERE e.RunningNumber IN (@runningNumber)
order by add_date desc
union all
select ext.Barcode
,RunningNumber as runnungNumber
,SsccEanRunningNumber as externalRunningNumber
,ArticleHumanReadableId
,case when LabelManagementHumanReadableId is null then (select HumanReadableId from [test1_AlplaPROD2.0_Read].[masterData].[LabelManagement] (nolock) where LabelMarkerId = 7) else LabelManagementHumanReadableId end as labelLayout
,case when EinlagerungsMengeSum IS NULL then 'notOnStock' else 'onStock' end as stockStatus
from [test1_AlplaPROD2.0_Read].[labelling].[ExternalLabel] (nolock) as ext
left join
alplaprod_test1.dbo.V_LagerPositionenBarcodes (nolock) as l on
ext.RunningNumber = l.Lfdnr
WHERE ext.SsccEanRunningNumber IN (@runningNumber) and
ext.RunningNumber NOT IN (
SELECT RunningNumber FROM [test1_AlplaPROD2.0_Read].[labelling].[InternalLabel] WHERE RunningNumber IN (@runningNumber)
)
`;