updates
This commit is contained in:
@@ -80,6 +80,15 @@ function numberFrom(value: unknown) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function parseSeqToBigInt(value: unknown): bigint | null {
|
||||
if (value === null || value === undefined) return null;
|
||||
if (typeof value === "number") {
|
||||
if (!Number.isInteger(value) || value < 0) return null;
|
||||
return BigInt(value);
|
||||
}
|
||||
if (typeof value === "string" && /^\d+$/.test(value)) return BigInt(value);
|
||||
return null;
|
||||
}
|
||||
|
||||
function canonicalText(value: unknown) {
|
||||
return String(value ?? "")
|
||||
@@ -262,6 +271,8 @@ export async function POST(req: Request) {
|
||||
|
||||
const machine = await getMachineAuth(String(machineId), apiKey);
|
||||
if (!machine) return NextResponse.json({ ok: false, error: "Unauthorized" }, { status: 401 });
|
||||
const bodySeq = parseSeqToBigInt(bodyRecord.seq);
|
||||
const bodySchemaVersion = clampText(bodyRecord.schemaVersion, 16);
|
||||
const orgSettings = await prisma.orgSettings.findUnique({
|
||||
where: { orgId: machine.orgId },
|
||||
select: { stoppageMultiplier: true, macroStoppageMultiplier: true, defaultsJson: true },
|
||||
@@ -410,35 +421,90 @@ export async function POST(req: Request) {
|
||||
const activeWorkOrder = asRecord(evRecord.activeWorkOrder);
|
||||
const dataActiveWorkOrder = asRecord(evData.activeWorkOrder);
|
||||
|
||||
const row = await prisma.machineEvent.create({
|
||||
data: {
|
||||
orgId: machine.orgId,
|
||||
machineId: machine.id,
|
||||
ts,
|
||||
topic: clampText(evRecord.topic ?? finalType, 64) ?? finalType,
|
||||
eventType: finalType,
|
||||
severity: sev,
|
||||
requiresAck: !!evRecord.requires_ack,
|
||||
title,
|
||||
description,
|
||||
data: toJsonValue(dataObj),
|
||||
workOrderId:
|
||||
clampText(evRecord.work_order_id, 64) ??
|
||||
clampText(evData.work_order_id, 64) ??
|
||||
clampText(activeWorkOrder?.id, 64) ??
|
||||
clampText(dataActiveWorkOrder?.id, 64) ??
|
||||
null,
|
||||
sku:
|
||||
clampText(evRecord.sku, 64) ??
|
||||
clampText(evData.sku, 64) ??
|
||||
clampText(activeWorkOrder?.sku, 64) ??
|
||||
clampText(dataActiveWorkOrder?.sku, 64) ??
|
||||
null,
|
||||
},
|
||||
// ✨ Cada evento puede traer su propio seq, o usar el del payload raíz
|
||||
const evSeq =
|
||||
parseSeqToBigInt(evRecord.seq) ??
|
||||
parseSeqToBigInt(evData.seq) ??
|
||||
bodySeq;
|
||||
|
||||
const evSchemaVersion =
|
||||
clampText(evRecord.schemaVersion, 16) ??
|
||||
bodySchemaVersion;
|
||||
|
||||
const eventData = {
|
||||
orgId: machine.orgId,
|
||||
machineId: machine.id,
|
||||
schemaVersion: evSchemaVersion,
|
||||
seq: evSeq,
|
||||
ts,
|
||||
topic: clampText(evRecord.topic ?? finalType, 64) ?? finalType,
|
||||
eventType: finalType,
|
||||
severity: sev,
|
||||
requiresAck: !!evRecord.requires_ack,
|
||||
title,
|
||||
description,
|
||||
data: toJsonValue(dataObj),
|
||||
workOrderId:
|
||||
clampText(evRecord.work_order_id, 64) ??
|
||||
clampText(evData.work_order_id, 64) ??
|
||||
clampText(activeWorkOrder?.id, 64) ??
|
||||
clampText(dataActiveWorkOrder?.id, 64) ??
|
||||
null,
|
||||
sku:
|
||||
clampText(evRecord.sku, 64) ??
|
||||
clampText(evData.sku, 64) ??
|
||||
clampText(activeWorkOrder?.sku, 64) ??
|
||||
clampText(dataActiveWorkOrder?.sku, 64) ??
|
||||
null,
|
||||
};
|
||||
|
||||
// ✨ Idempotente: si ya existe (mismo orgId+machineId+seq), no inserta
|
||||
const insertResult = await prisma.machineEvent.createMany({
|
||||
data: [eventData],
|
||||
skipDuplicates: true,
|
||||
});
|
||||
|
||||
// ✨ Buscar la fila (la recién creada o la duplicada existente)
|
||||
let row;
|
||||
if (evSeq != null) {
|
||||
row = await prisma.machineEvent.findFirst({
|
||||
where: {
|
||||
orgId: machine.orgId,
|
||||
machineId: machine.id,
|
||||
seq: evSeq,
|
||||
},
|
||||
orderBy: { ts: "asc" },
|
||||
});
|
||||
} else {
|
||||
// Sin seq, buscar por ts (fallback compatibilidad con eventos viejos)
|
||||
row = await prisma.machineEvent.findFirst({
|
||||
where: {
|
||||
orgId: machine.orgId,
|
||||
machineId: machine.id,
|
||||
ts,
|
||||
eventType: finalType,
|
||||
},
|
||||
orderBy: { ts: "desc" },
|
||||
});
|
||||
}
|
||||
|
||||
if (!row) {
|
||||
skipped.push({ reason: "row_not_found_after_insert", seq: evSeq?.toString() });
|
||||
continue;
|
||||
}
|
||||
|
||||
const wasDuplicate = insertResult.count === 0;
|
||||
|
||||
// Si fue duplicado, no procesar reasonEntry ni alertas (ya se hicieron antes)
|
||||
if (wasDuplicate) {
|
||||
created.push({ id: row.id, ts: row.ts, eventType: row.eventType });
|
||||
continue; // ✨ saltar el resto del procesamiento
|
||||
}
|
||||
|
||||
created.push({ id: row.id, ts: row.ts, eventType: row.eventType });
|
||||
|
||||
|
||||
|
||||
// If the payload carries a `reason`, create the corresponding ReasonEntry.
|
||||
// If it doesn't, still create an "UNCLASSIFIED" downtime ReasonEntry for stop events so the dashboard can show coverage.
|
||||
if (evRecord.is_update || evRecord.is_auto_ack || dataObj.is_update || dataObj.is_auto_ack){
|
||||
|
||||
Reference in New Issue
Block a user