feat(notifications): migrated all from v1
This commit is contained in:
13
database/migrations/0033_flimsy_salo.sql
Normal file
13
database/migrations/0033_flimsy_salo.sql
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
CREATE TABLE "notifications" (
|
||||||
|
"notify_id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"description" text NOT NULL,
|
||||||
|
"checkInterval" text DEFAULT '1',
|
||||||
|
"timeType" text DEFAULT 'hour',
|
||||||
|
"emails" text,
|
||||||
|
"active" boolean DEFAULT false,
|
||||||
|
"lastRan" timestamp DEFAULT now(),
|
||||||
|
"notifiySettings" jsonb DEFAULT '{}'::jsonb
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX "notify_name" ON "notifications" USING btree ("name");
|
||||||
1360
database/migrations/meta/0033_snapshot.json
Normal file
1360
database/migrations/meta/0033_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -232,6 +232,13 @@
|
|||||||
"when": 1743124980863,
|
"when": 1743124980863,
|
||||||
"tag": "0032_tough_iron_monger",
|
"tag": "0032_tough_iron_monger",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 33,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1743424730855,
|
||||||
|
"tag": "0033_flimsy_salo",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
36
database/schema/notifications.ts
Normal file
36
database/schema/notifications.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import {
|
||||||
|
boolean,
|
||||||
|
jsonb,
|
||||||
|
pgTable,
|
||||||
|
text,
|
||||||
|
timestamp,
|
||||||
|
uniqueIndex,
|
||||||
|
uuid,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
|
import { createSelectSchema } from "drizzle-zod";
|
||||||
|
|
||||||
|
export const notifications = pgTable(
|
||||||
|
"notifications",
|
||||||
|
{
|
||||||
|
notify_id: uuid("notify_id").defaultRandom().primaryKey(),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
description: text("description").notNull(),
|
||||||
|
checkInterval: text("checkInterval").default("1"),
|
||||||
|
timeType: text("timeType").default("hour"),
|
||||||
|
emails: text("emails"),
|
||||||
|
active: boolean("active").default(false),
|
||||||
|
lastRan: timestamp("lastRan").defaultNow(),
|
||||||
|
notifiySettings: jsonb("notifiySettings").default({}),
|
||||||
|
},
|
||||||
|
(table) => [
|
||||||
|
// uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`),
|
||||||
|
uniqueIndex("notify_name").on(table.name),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Schema for inserting a user - can be used to validate API requests
|
||||||
|
// export const insertRolesSchema = createInsertSchema(roles, {
|
||||||
|
// name: z.string().min(3, {message: "Role name must be more than 3 letters"}),
|
||||||
|
// });
|
||||||
|
// Schema for selecting a Expenses - can be used to validate API responses
|
||||||
|
export const selectNotificationsSchema = createSelectSchema(notifications);
|
||||||
18012
package-lock.json
generated
18012
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,143 @@
|
|||||||
|
// SELECT count(*) FROM V_EtikettenGedruckt where AnzahlGedruckterKopien > 2 and CONVERT(varchar(5), Add_Date,108) not like CONVERT(varchar(5), Upd_Date,108) and Upd_Date > DATEADD(SECOND, -30,getdate()) and VpkVorschriftBez not like '%$%'
|
||||||
|
|
||||||
|
import { eq, sql } from "drizzle-orm";
|
||||||
|
import { db } from "../../../../../database/dbclient.js";
|
||||||
|
import { notifications } from "../../../../../database/schema/notifications.js";
|
||||||
|
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
|
||||||
|
import { createLog } from "../../../logger/logger.js";
|
||||||
|
import { sendEmail } from "../sendMail.js";
|
||||||
|
import { query } from "../../../sqlServer/prodSqlServer.js";
|
||||||
|
|
||||||
|
export interface DownTime {
|
||||||
|
downTimeId?: number;
|
||||||
|
machineAlias?: string;
|
||||||
|
}
|
||||||
|
export default async function reprintLabelMonitor(notifyData: any) {
|
||||||
|
// we will over ride this with users that want to sub to this
|
||||||
|
// a new table will be called subalerts and link to the do a kinda linkn where the user wants it then it dose subId: 1, userID: x, notificationId: y. then in here we look up the userid to get the email :D
|
||||||
|
// this could then leave the emails in the notificaion blank and let users sub to it.
|
||||||
|
if (notifyData.emails === "") {
|
||||||
|
createLog(
|
||||||
|
"error",
|
||||||
|
"notify",
|
||||||
|
"notify",
|
||||||
|
`There are no emails set for ${notifyData.name}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log(data.secondarySetting[0].duration);
|
||||||
|
let dQuery = `
|
||||||
|
SELECT
|
||||||
|
[IdHistoryStillstandsereignis] as downTimeId
|
||||||
|
,DATEDIFF(MINUTE,b.[Startzeit], b.[Endzeit]) as totalDuration
|
||||||
|
--, b.[IdMaschine]
|
||||||
|
,x.[Bezeichnung] as machineAlias
|
||||||
|
--,b.[IdStillstandsGrund],
|
||||||
|
, c.CTO_Code
|
||||||
|
,c.Downtime_Description
|
||||||
|
--,b.[IdFehlermerkmal],
|
||||||
|
,case when g.DT_Group_Desc is null then 'Not assigned yet' else g.DT_Group_Desc end as groupDesc
|
||||||
|
,b.[Bemerkung] as remark
|
||||||
|
,CONVERT(VARCHAR, CAST(b.[Startzeit] AS DATETIME), 100) dtStart
|
||||||
|
,CONVERT(VARCHAR, CAST(b.[Endzeit] AS DATETIME), 100) dtEnd
|
||||||
|
|
||||||
|
FROM Alplaprod_test1.[dbo].[T_HistoryStillstandsereignis] (nolock)b
|
||||||
|
|
||||||
|
--get the machine info
|
||||||
|
left join
|
||||||
|
Alplaprod_test1.[dbo].[T_Maschine] (nolock)x
|
||||||
|
on b.IdMaschine = x.IdMaschine
|
||||||
|
|
||||||
|
-- add in the cto codes
|
||||||
|
left join
|
||||||
|
Alplaprod_test1.[dbo].[V_MES_Downtime_Reasons] (nolock)c
|
||||||
|
on b.IdStillstandsGrund = c.Local_Downtime_ID
|
||||||
|
|
||||||
|
left join
|
||||||
|
Alplaprod_test1.[dbo].[V_MES_Downtime_Characteristics] (nolock)g
|
||||||
|
on b.IdFehlermerkmal = g.Local_DT_Characteristic_Id
|
||||||
|
|
||||||
|
|
||||||
|
where DATEDIFF(MINUTE,b.[Startzeit],b.[Endzeit]) > ${
|
||||||
|
notifyData.notifiySettings
|
||||||
|
? notifyData.notifiySettings?.duration
|
||||||
|
: 10
|
||||||
|
}
|
||||||
|
and b.[Startzeit] > getDate() - ${
|
||||||
|
notifyData.notifiySettings
|
||||||
|
? notifyData.notifiySettings?.daysInPast
|
||||||
|
: 10
|
||||||
|
} --adding this date check in so we dont get everything possible
|
||||||
|
and c.CTO_Code not like 'a%'
|
||||||
|
and c.CTO_Code not like 'b%'
|
||||||
|
and c.CTO_Code not like 'c%'
|
||||||
|
and c.CTO_Code not like 'd%'
|
||||||
|
and c.CTO_Code not like 'e%'
|
||||||
|
and c.CTO_Code not like 'f%'
|
||||||
|
and c.CTO_Code not like 'y%'
|
||||||
|
order by IdHistoryStillstandsereignis desc
|
||||||
|
`;
|
||||||
|
|
||||||
|
//console.log(query);
|
||||||
|
let downTime: any; //DownTime[];
|
||||||
|
try {
|
||||||
|
downTime = await query(dQuery, "downTimeCheck");
|
||||||
|
//console.log(labels.length);
|
||||||
|
|
||||||
|
if (
|
||||||
|
downTime.length > 0 &&
|
||||||
|
downTime[0]?.downTimeId > notifyData.notifiySettings.prodID
|
||||||
|
) {
|
||||||
|
//send the email :D
|
||||||
|
const emailSetup = {
|
||||||
|
emailTo: notifyData.emails,
|
||||||
|
subject: `Alert! Downtime recorded greater than ${
|
||||||
|
notifyData.notifiySettings?.duration
|
||||||
|
}min ${
|
||||||
|
downTime.length === 1
|
||||||
|
? `on ${downTime[0].machineAlias}`
|
||||||
|
: ""
|
||||||
|
}`,
|
||||||
|
template: "downTimeCheck",
|
||||||
|
context: {
|
||||||
|
items: downTime,
|
||||||
|
secondarySetting: notifyData.notifiySettings,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const sentEmail = await sendEmail(emailSetup);
|
||||||
|
|
||||||
|
if (!sentEmail.success) {
|
||||||
|
createLog(
|
||||||
|
"error",
|
||||||
|
"notify",
|
||||||
|
"notify",
|
||||||
|
"Failed to send email, will try again on next interval"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, error } = await tryCatch(
|
||||||
|
db
|
||||||
|
.update(notifications)
|
||||||
|
.set({
|
||||||
|
lastRan: sql`NOW()`,
|
||||||
|
notifiySettings: {
|
||||||
|
...notifyData.notifiySettings,
|
||||||
|
prodID: downTime[0].downTimeId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.where(eq(notifications.name, notifyData.name))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
createLog(
|
||||||
|
"error",
|
||||||
|
"notify",
|
||||||
|
"notify",
|
||||||
|
`Error from running the downtimeCheck query: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { eq, sql } from "drizzle-orm";
|
||||||
|
import { db } from "../../../../../database/dbclient.js";
|
||||||
|
import { notifications } from "../../../../../database/schema/notifications.js";
|
||||||
|
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
|
||||||
|
import { createLog } from "../../../logger/logger.js";
|
||||||
|
|
||||||
|
const notification = async (notifyData: any) => {
|
||||||
|
/**
|
||||||
|
* Pass the entire notification over
|
||||||
|
*/
|
||||||
|
createLog("info", "notify", "notify", `monitoring ${notifyData.name}`);
|
||||||
|
|
||||||
|
const { data, error } = await tryCatch(
|
||||||
|
db
|
||||||
|
.update(notifications)
|
||||||
|
.set({
|
||||||
|
lastRan: sql`NOW()`,
|
||||||
|
// notifiySettings: {
|
||||||
|
// ...updateSettings,
|
||||||
|
// prodID: labels[0].IdEtikettenHistorie,
|
||||||
|
// },
|
||||||
|
})
|
||||||
|
.where(eq(notifications.name, notifyData.name))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default notification;
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
// SELECT count(*) FROM V_EtikettenGedruckt where AnzahlGedruckterKopien > 2 and CONVERT(varchar(5), Add_Date,108) not like CONVERT(varchar(5), Upd_Date,108) and Upd_Date > DATEADD(SECOND, -30,getdate()) and VpkVorschriftBez not like '%$%'
|
||||||
|
import { isWeekend } from "date-fns";
|
||||||
|
import { createLog } from "../../../logger/logger.js";
|
||||||
|
import { query } from "../../../sqlServer/prodSqlServer.js";
|
||||||
|
import { sendEmail } from "../sendMail.js";
|
||||||
|
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
|
||||||
|
import { db } from "../../../../../database/dbclient.js";
|
||||||
|
import { notifications } from "../../../../../database/schema/notifications.js";
|
||||||
|
import { eq, sql } from "drizzle-orm";
|
||||||
|
|
||||||
|
export interface PPOO {
|
||||||
|
IdPosition?: number;
|
||||||
|
}
|
||||||
|
export default async function reprintLabelMonitor(notifyData: any) {
|
||||||
|
createLog("info", "notify", "notify", `monitoring ${notifyData.name}`);
|
||||||
|
if (notifyData.emails === "") {
|
||||||
|
createLog(
|
||||||
|
"error",
|
||||||
|
"notify",
|
||||||
|
"notify",
|
||||||
|
`There are no emails set for ${notifyData.name}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the secondarySetting back to json to use it.
|
||||||
|
// notifyData = { ...notifyData, secondarySetting: JSON.parse(notifyData.secondarySetting) };
|
||||||
|
|
||||||
|
// as this one goes to managers we want to not send on the weekends
|
||||||
|
|
||||||
|
const weekend = isWeekend(new Date(Date.now()));
|
||||||
|
|
||||||
|
if (weekend && notifyData.notifiySettings.weekend) {
|
||||||
|
createLog(
|
||||||
|
"info",
|
||||||
|
"notify",
|
||||||
|
"notify",
|
||||||
|
`${notifyData.name} will not run on the weekends`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let notifyQuery = `
|
||||||
|
SELECT
|
||||||
|
--[EinlagerungsDatummin] as lastMovingDate,
|
||||||
|
round(VerfuegbareMengeVPKSum,2) as pallets
|
||||||
|
,VerfuegbareMengeSum as total
|
||||||
|
,round([GesperrteMengeVpkSum],2) as held
|
||||||
|
,round([GesperrteMengeSum],2) as heldQty
|
||||||
|
,[IdArtikelVarianten] as av
|
||||||
|
,[IdProdBereich] as pfcID
|
||||||
|
,[ArtikelVariantenBez] as articleDescription
|
||||||
|
,[ArtikelVariantenAlias] as articleDescriptionAlias
|
||||||
|
,[LagerAbteilungKurzBez] as location
|
||||||
|
,[Lfdnr] as runningNumber
|
||||||
|
,[Produktionslos] as lot
|
||||||
|
,[ProduktionsDatumMin] as productionDate
|
||||||
|
,IdPosition
|
||||||
|
FROM [AlplaPROD_test1].[dbo].[V_LagerPositionenBarcodes] (nolock)
|
||||||
|
|
||||||
|
where idlagerabteilung in ([locations]) and [ProduktionsDatumMin] < DATEadd( Hour, -[timeCheck], getdate())
|
||||||
|
|
||||||
|
order by [ProduktionsDatumMin] asc
|
||||||
|
`;
|
||||||
|
|
||||||
|
//update the time check
|
||||||
|
notifyQuery = notifyQuery.replaceAll("[timeCheck]", notifyData.checkTime);
|
||||||
|
notifyQuery = notifyQuery.replaceAll(
|
||||||
|
"[locations]",
|
||||||
|
notifyData.notifiySettings.locations
|
||||||
|
);
|
||||||
|
|
||||||
|
let prod: PPOO[];
|
||||||
|
try {
|
||||||
|
prod = await query(notifyQuery, "Label Reprints");
|
||||||
|
//console.log(labels.length);
|
||||||
|
// const now = Date.now()
|
||||||
|
if (prod.length > 0) {
|
||||||
|
//send the email :D
|
||||||
|
|
||||||
|
// update the count with the result
|
||||||
|
|
||||||
|
const emailSetup = {
|
||||||
|
emailTo: notifyData.emails,
|
||||||
|
subject: `Alert! Pallets in production greater than ${notifyData.checkTime} ${notifyData.timeType}`,
|
||||||
|
template: "productionCheck",
|
||||||
|
context: {
|
||||||
|
items: prod,
|
||||||
|
count: prod.length,
|
||||||
|
checkTime: notifyData.checkTime,
|
||||||
|
timeCheck: notifyData.timeType,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const sentEmail = await sendEmail(emailSetup);
|
||||||
|
|
||||||
|
if (!sentEmail.success) {
|
||||||
|
createLog(
|
||||||
|
"error",
|
||||||
|
"notify",
|
||||||
|
"notify",
|
||||||
|
"Failed to send email, will try again on next interval"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let updateSettings = notifyData.notifiySettings;
|
||||||
|
const { data, error } = await tryCatch(
|
||||||
|
db
|
||||||
|
.update(notifications)
|
||||||
|
.set({
|
||||||
|
lastRan: sql`NOW()`,
|
||||||
|
notifiySettings: {
|
||||||
|
...updateSettings,
|
||||||
|
count: prod.length,
|
||||||
|
prodID: prod[0].IdPosition,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.where(eq(notifications.name, notifyData.name))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
createLog(
|
||||||
|
"error",
|
||||||
|
"sql",
|
||||||
|
"error",
|
||||||
|
`Error from running the Label Reprints query: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
// SELECT count(*) FROM V_EtikettenGedruckt where AnzahlGedruckterKopien > 2 and CONVERT(varchar(5), Add_Date,108) not like CONVERT(varchar(5), Upd_Date,108) and Upd_Date > DATEADD(SECOND, -30,getdate()) and VpkVorschriftBez not like '%$%'
|
||||||
|
|
||||||
|
import { eq, sql } from "drizzle-orm";
|
||||||
|
import { db } from "../../../../../database/dbclient.js";
|
||||||
|
import { notifications } from "../../../../../database/schema/notifications.js";
|
||||||
|
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
|
||||||
|
import { createLog } from "../../../logger/logger.js";
|
||||||
|
import { query } from "../../../sqlServer/prodSqlServer.js";
|
||||||
|
import { sendEmail } from "../sendMail.js";
|
||||||
|
|
||||||
|
export interface Blocking {
|
||||||
|
HumanReadableId?: number;
|
||||||
|
subject?: string;
|
||||||
|
}
|
||||||
|
export default async function qualityBlockingMonitor(notifyData: any) {
|
||||||
|
createLog("info", "notify", "notify", `monitoring ${notifyData.name}`);
|
||||||
|
if (notifyData.emails === "") {
|
||||||
|
createLog(
|
||||||
|
"error",
|
||||||
|
"notify",
|
||||||
|
"notify",
|
||||||
|
`There are no emails set for ${notifyData.name}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let blockQuery = `
|
||||||
|
SELECT
|
||||||
|
'Alert! new blocking order: #' + cast(HumanReadableId as varchar) + ' - ' + ArticleVariantDescription as subject,
|
||||||
|
cast([HumanReadableId] as varchar) as blockingNumber,
|
||||||
|
[ArticleVariantDescription] as article,
|
||||||
|
cast([CustomerHumanReadableId] as varchar) + ' - ' + [CustomerDescription] as customer,
|
||||||
|
convert(varchar(10), [test1_AlplaPROD2.0_Reporting].[reporting_blocking].[BlockingOrder].[BlockingDate], 101) + ' - ' + convert(varchar(5), [test1_AlplaPROD2.0_Reporting].[reporting_blocking].[BlockingOrder].[BlockingDate], 108) as blockingDate,
|
||||||
|
cast(ArticleVariantHumanReadableId as varchar) + ' - ' + ArticleVariantDescription as av,
|
||||||
|
case when [test1_AlplaPROD2.0_Reporting].[reporting_blocking].[BlockingOrder].Remark = '' or [test1_AlplaPROD2.0_Reporting].[reporting_blocking].[BlockingOrder].Remark is NULL then 'Please reach out to quality for the reason this was placed on hold as a remark was not entered during the blocking processs' else [test1_AlplaPROD2.0_Reporting].[reporting_blocking].[BlockingOrder].Remark end as remark,
|
||||||
|
cast(FORMAT(TotalAmountOfPieces, '###,###') as varchar) + ' / ' + cast(LoadingUnit as varchar) as peicesAndLoadingUnits,
|
||||||
|
[test1_AlplaPROD2.0_Reporting].[reporting_blocking].[BlockingOrder].ProductionLotHumanReadableId as lotNumber,
|
||||||
|
cast(IdGlobalBlockingDefectsGroup as varchar) + ' - ' + BD.Description as mainDefectGroup,
|
||||||
|
cast(IdGlobalBlockingDefect as varchar) + ' - ' + MD.Description as mainDefect,
|
||||||
|
sent=0,
|
||||||
|
lot.MachineLocation as line,
|
||||||
|
HumanReadableId
|
||||||
|
FROM [test1_AlplaPROD2.0_Reporting].[reporting_blocking].[BlockingOrder] (nolock)
|
||||||
|
|
||||||
|
/*** Join 1.0 table to get correct id info to link ***/
|
||||||
|
join
|
||||||
|
[AlplaPROD_test1].[dbo].[T_BlockingOrders] (nolock) AS BO
|
||||||
|
on [HumanReadableId] = BO.[IdBlockingOrder]
|
||||||
|
|
||||||
|
|
||||||
|
/*** Get the main defect info ***/
|
||||||
|
Inner join
|
||||||
|
[AlplaPROD_test1].[dbo].[T_BlockingDefectsGroups] (nolock) as BD
|
||||||
|
ON BO.IdMainDefectGroup = BD.IdBlockingDefectsGroup
|
||||||
|
|
||||||
|
INNER join
|
||||||
|
[AlplaPROD_test1].[dbo].[T_BlockingDefects] as MD
|
||||||
|
ON BO.IdMainDefect = MD.IdBlockingDefect
|
||||||
|
/*** get lot info ***/
|
||||||
|
|
||||||
|
left join
|
||||||
|
(SELECT [MachineLocation]
|
||||||
|
,[MachineDescription]
|
||||||
|
,[ProductionLotHumanReadableId]
|
||||||
|
FROM [test1_AlplaPROD2.0_Reporting].[reporting_productionControlling].[ProducedLot]) as lot
|
||||||
|
on [test1_AlplaPROD2.0_Reporting].[reporting_blocking].[BlockingOrder].ProductionLotHumanReadableId = lot.ProductionLotHumanReadableId
|
||||||
|
|
||||||
|
where [test1_AlplaPROD2.0_Reporting].[reporting_blocking].[BlockingOrder].[BlockingDate] between getdate() - 1 and getdate() + 1
|
||||||
|
and [test1_AlplaPROD2.0_Reporting].[reporting_blocking].[BlockingOrder].BlockingTrigger = 1
|
||||||
|
and HumanReadableId NOT IN ([sentBlockingOrders])
|
||||||
|
`;
|
||||||
|
|
||||||
|
//add the blocking orders in.
|
||||||
|
blockQuery = blockQuery.replaceAll(
|
||||||
|
"[sentBlockingOrders]",
|
||||||
|
notifyData.sentBlocking[0].sentBlockingOrders
|
||||||
|
);
|
||||||
|
|
||||||
|
let blocking: any;
|
||||||
|
try {
|
||||||
|
blocking = await query(blockQuery, "Quality Blocking");
|
||||||
|
//console.log(labels.length);
|
||||||
|
// const now = Date.now()
|
||||||
|
//console.log(blocking);
|
||||||
|
// console.log(blocking[0].blockingNumber > data.prodID);
|
||||||
|
if (
|
||||||
|
blocking.length > 0 &&
|
||||||
|
blocking[0].HumanReadableId > notifyData.notifiySettings.prodID
|
||||||
|
) {
|
||||||
|
//send the email :D
|
||||||
|
const emailSetup = {
|
||||||
|
emailTo: notifyData.emails,
|
||||||
|
subject:
|
||||||
|
blocking.length > 0
|
||||||
|
? `Alert! New blocking orders.`
|
||||||
|
: blocking[0].subject,
|
||||||
|
template: "qualityBlocking",
|
||||||
|
context: {
|
||||||
|
items: blocking,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const sentEmail = await sendEmail(emailSetup);
|
||||||
|
|
||||||
|
if (!sentEmail.success) {
|
||||||
|
createLog(
|
||||||
|
"error",
|
||||||
|
"nofity",
|
||||||
|
"notify",
|
||||||
|
"Failed to send email, will try again on next interval"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the new blocking order to this
|
||||||
|
const newBlockingOrders = blocking.map(
|
||||||
|
(b: any) => b.HumanReadableId
|
||||||
|
);
|
||||||
|
|
||||||
|
//console.log(newBlockingOrders);
|
||||||
|
//console.log(sentBlocking[0].sentBlockingOrders);
|
||||||
|
// Ensure no duplicates
|
||||||
|
const uniqueOrders = Array.from(
|
||||||
|
new Set([
|
||||||
|
...notifyData.sentBlocking[0].sentBlockingOrders,
|
||||||
|
...newBlockingOrders,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update sentBlockingOrders
|
||||||
|
notifyData.sentBlocking[0].sentBlockingOrders = uniqueOrders;
|
||||||
|
|
||||||
|
//console.log(notifUpdate);
|
||||||
|
|
||||||
|
const { data, error } = await tryCatch(
|
||||||
|
db
|
||||||
|
.update(notifications)
|
||||||
|
.set({
|
||||||
|
lastRan: sql`NOW()`,
|
||||||
|
notifiySettings: {
|
||||||
|
...notifyData.notifiySettings,
|
||||||
|
prodID: blocking[0].HumanReadableId,
|
||||||
|
sentBlockingOrders: uniqueOrders,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.where(eq(notifications.name, notifyData.name))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
createLog(
|
||||||
|
"error",
|
||||||
|
"notify",
|
||||||
|
"notify",
|
||||||
|
`Error from running the blocking query: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import { eq, sql } from "drizzle-orm";
|
||||||
|
import { db } from "../../../../../database/dbclient.js";
|
||||||
|
import { notifications } from "../../../../../database/schema/notifications.js";
|
||||||
|
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
|
||||||
|
import { createLog } from "../../../logger/logger.js";
|
||||||
|
import { query } from "../../../sqlServer/prodSqlServer.js";
|
||||||
|
import { sendEmail } from "../sendMail.js";
|
||||||
|
|
||||||
|
export interface Labels {
|
||||||
|
IdEtikettenHistorie?: number;
|
||||||
|
}
|
||||||
|
const notification = async (notifyData: any) => {
|
||||||
|
/**
|
||||||
|
* Pass the entire notification over
|
||||||
|
*/
|
||||||
|
createLog("info", "notify", "notify", `monitoring ${notifyData.name}`);
|
||||||
|
|
||||||
|
// validate if there are any emails.
|
||||||
|
if (notifyData.emails === "") {
|
||||||
|
createLog(
|
||||||
|
"error",
|
||||||
|
"notify",
|
||||||
|
"notify",
|
||||||
|
`There are no emails set for ${notifyData.name}`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// well set a backup default time here
|
||||||
|
let timeCheck = `DATEADD(SECOND, -30, getdate()) `;
|
||||||
|
|
||||||
|
// set the time of getting the label
|
||||||
|
if (notifyData.timeType === "sec") {
|
||||||
|
timeCheck = `DATEADD(SECOND, -${notifyData.checkTime}, getdate()) `;
|
||||||
|
} else if (notifyData.timeType === "min") {
|
||||||
|
timeCheck = `DATEADD(MINUTE, -${notifyData.checkTime}, getdate()) `;
|
||||||
|
}
|
||||||
|
|
||||||
|
let reprintQuery = `
|
||||||
|
SELECT
|
||||||
|
IdArtikelvarianten as av,
|
||||||
|
ArtikelVariantenBez as alias,
|
||||||
|
LfdNr as runningNumber,
|
||||||
|
CONVERT(VARCHAR, CAST(Add_Date AS DATETIME), 100) Add_Date,
|
||||||
|
Add_User,
|
||||||
|
CONVERT(VARCHAR, CAST(Upd_Date AS DATETIME), 100) Upd_Date,
|
||||||
|
Upd_User,
|
||||||
|
EtikettenDruckerBezeichnung as printer,
|
||||||
|
AnzahlGedruckterKopien as totalPrinted
|
||||||
|
FROM Alplaprod_test1.dbo.V_EtikettenGedruckt (nolock)
|
||||||
|
where AnzahlGedruckterKopien > 2
|
||||||
|
and CONVERT(varchar(5), Add_Date,108) not like CONVERT(varchar(5), Upd_Date,108)
|
||||||
|
and Upd_Date > DATEADD(SECOND, -30, getdate())
|
||||||
|
and VpkVorschriftBez not like '%$%'
|
||||||
|
`;
|
||||||
|
|
||||||
|
//update the time check
|
||||||
|
reprintQuery = reprintQuery.replaceAll(
|
||||||
|
"DATEADD(SECOND, -30, getdate()) ",
|
||||||
|
timeCheck
|
||||||
|
);
|
||||||
|
|
||||||
|
//let labels: Labels[];
|
||||||
|
|
||||||
|
const { data: labels, error: labelError } = await tryCatch(
|
||||||
|
query(reprintQuery, "Label Reprints")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (labels.length > 0) {
|
||||||
|
//send the email :D
|
||||||
|
const emailSetup = {
|
||||||
|
emailTo: notifyData.emails,
|
||||||
|
subject: "Alert! Label Reprinted",
|
||||||
|
template: "reprintLabels",
|
||||||
|
context: {
|
||||||
|
items: labels,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const sentEmail = await sendEmail(emailSetup);
|
||||||
|
|
||||||
|
if (!sentEmail.success) {
|
||||||
|
createLog(
|
||||||
|
"error",
|
||||||
|
"notify",
|
||||||
|
"notify",
|
||||||
|
"Failed to send email, will try again on next interval"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// // update the last time we ran and the prod id
|
||||||
|
// const notifUpdate = {
|
||||||
|
// prodID: labels[0].IdEtikettenHistorie,
|
||||||
|
// lastRan: nowDate(),
|
||||||
|
// };
|
||||||
|
|
||||||
|
// update the last time ran
|
||||||
|
const updateSettings = notifyData.notifiySettings;
|
||||||
|
|
||||||
|
const { data, error } = await tryCatch(
|
||||||
|
db
|
||||||
|
.update(notifications)
|
||||||
|
.set({
|
||||||
|
lastRan: sql`NOW()`,
|
||||||
|
notifiySettings: {
|
||||||
|
...updateSettings,
|
||||||
|
prodID: labels[0].IdEtikettenHistorie,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.where(eq(notifications.name, notifyData.name))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default notification;
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
// SELECT count(*) FROM V_EtikettenGedruckt where AnzahlGedruckterKopien > 2 and CONVERT(varchar(5), Add_Date,108) not like CONVERT(varchar(5), Upd_Date,108) and Upd_Date > DATEADD(SECOND, -30,getdate()) and VpkVorschriftBez not like '%$%'
|
||||||
|
|
||||||
|
import { isWeekend } from "date-fns";
|
||||||
|
import { createLog } from "../../../logger/logger.js";
|
||||||
|
import { sendEmail } from "../sendMail.js";
|
||||||
|
import { query } from "../../../sqlServer/prodSqlServer.js";
|
||||||
|
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
|
||||||
|
import { notifications } from "../../../../../database/schema/notifications.js";
|
||||||
|
import { eq, sql } from "drizzle-orm";
|
||||||
|
import { db } from "../../../../../database/dbclient.js";
|
||||||
|
|
||||||
|
export interface PPOO {
|
||||||
|
IdPosition?: number;
|
||||||
|
}
|
||||||
|
export default async function reprintLabelMonitor(notifyData: any) {
|
||||||
|
createLog("info", "notify", "notify", `monitoring ${notifyData.name}`);
|
||||||
|
if (notifyData.emails === "") {
|
||||||
|
createLog(
|
||||||
|
"error",
|
||||||
|
"notify",
|
||||||
|
"notify",
|
||||||
|
`There are no emails set for ${notifyData.notificationName}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// as this one goes to managers we want to not send on the weekends
|
||||||
|
|
||||||
|
const weekend = isWeekend(new Date(Date.now()));
|
||||||
|
|
||||||
|
if (weekend && notifyData.notifiySettings.weekend) {
|
||||||
|
createLog(
|
||||||
|
"info",
|
||||||
|
"notify",
|
||||||
|
"notify",
|
||||||
|
`${notifyData.name} will not run on the weekends`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let noteQuery = `
|
||||||
|
SELECT
|
||||||
|
--[EinlagerungsDatummin] as lastMovingDate,
|
||||||
|
round(VerfuegbareMengeVPKSum,2) as pallets
|
||||||
|
,VerfuegbareMengeSum as total
|
||||||
|
,round([GesperrteMengeVpkSum],2) as held
|
||||||
|
,round([GesperrteMengeSum],2) as heldQty
|
||||||
|
,[IdArtikelVarianten] as av
|
||||||
|
,[IdProdBereich] as pfcID
|
||||||
|
,[ArtikelVariantenBez] as articleDescription
|
||||||
|
,[ArtikelVariantenAlias] as articleDescriptionAlias
|
||||||
|
,[LagerAbteilungKurzBez] as location
|
||||||
|
,[Lfdnr] as runningNumber
|
||||||
|
,[Produktionslos] as lot
|
||||||
|
,[ProduktionsDatumMin] as productionDate
|
||||||
|
,IdPosition
|
||||||
|
FROM [AlplaPROD_test1].[dbo].[V_LagerPositionenBarcodes] (nolock)
|
||||||
|
|
||||||
|
where idlagerabteilung in ([locations]) and [ProduktionsDatumMin] < DATEadd( Hour, -[timeCheck], getdate())
|
||||||
|
|
||||||
|
order by [ProduktionsDatumMin] asc
|
||||||
|
`;
|
||||||
|
|
||||||
|
//update the time check
|
||||||
|
noteQuery = noteQuery
|
||||||
|
.replaceAll("[timeCheck]", notifyData.checkTime)
|
||||||
|
.replaceAll("[locations]", notifyData.notifiySettings.locations);
|
||||||
|
|
||||||
|
let stage: PPOO[];
|
||||||
|
try {
|
||||||
|
stage = await query(noteQuery, "Staging checks");
|
||||||
|
//console.log(labels.length);
|
||||||
|
// const now = Date.now()
|
||||||
|
if (stage.length > 0) {
|
||||||
|
//send the email :D
|
||||||
|
|
||||||
|
// update the count with the result
|
||||||
|
|
||||||
|
const emailSetup = {
|
||||||
|
emailTo: notifyData.emails,
|
||||||
|
subject: `Alert! Pallets in staging greater than ${notifyData.checkTime} ${notifyData.timeType}`,
|
||||||
|
template: "stagingCheck",
|
||||||
|
context: {
|
||||||
|
items: stage,
|
||||||
|
count: stage.length,
|
||||||
|
checkTime: notifyData.checkTime,
|
||||||
|
timeCheck: notifyData.timeType,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const sentEmail = await sendEmail(emailSetup);
|
||||||
|
|
||||||
|
if (!sentEmail.success) {
|
||||||
|
createLog(
|
||||||
|
"error",
|
||||||
|
"notify",
|
||||||
|
"notify",
|
||||||
|
"Failed to send email, will try again on next interval"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the last time we ran and the prod id
|
||||||
|
let updateSettings = notifyData.notifiySettings;
|
||||||
|
const { data, error } = await tryCatch(
|
||||||
|
db
|
||||||
|
.update(notifications)
|
||||||
|
.set({
|
||||||
|
lastRan: sql`NOW()`,
|
||||||
|
notifiySettings: {
|
||||||
|
...updateSettings,
|
||||||
|
count: stage.length,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.where(eq(notifications.name, notifyData.name))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
createLog(
|
||||||
|
"error",
|
||||||
|
"notify",
|
||||||
|
"notify",
|
||||||
|
`Error from running the Label Reprints query: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
export let xmlPayloadTI = `
|
||||||
|
<service-request>
|
||||||
|
<service-id>ImportWeb</service-id>
|
||||||
|
<request-id>[requestID]</request-id>
|
||||||
|
<data>
|
||||||
|
<WebImport>
|
||||||
|
[WebImportHeader]
|
||||||
|
<WebImportFile>
|
||||||
|
<MercuryGate>
|
||||||
|
<Header>
|
||||||
|
<SenderID/>
|
||||||
|
<ReceiverID/>
|
||||||
|
<DocTypeID>MasterBillOfLading</DocTypeID>
|
||||||
|
<DocCount>1</DocCount>
|
||||||
|
</Header>
|
||||||
|
<Load action="UpdateOrAdd">
|
||||||
|
<Enterprise name="" customerAcctNum="[customerAccountNum]"/>
|
||||||
|
<AssignedTo/>
|
||||||
|
<ReferenceNumbers>
|
||||||
|
<ReferenceNumber type="Load Number" isPrimary="true">[loadNumber]</ReferenceNumber>
|
||||||
|
</ReferenceNumbers>
|
||||||
|
<Payment>
|
||||||
|
<Method>Prepaid</Method>
|
||||||
|
<BillTo thirdParty="False">
|
||||||
|
<Address Type="BillTo" isResidential="False">
|
||||||
|
<Alias/>
|
||||||
|
<Name>ALPLA</Name>
|
||||||
|
<AddrLine1>CO TRANSPORTATION INSIGHT</AddrLine1>
|
||||||
|
<AddrLine2>PO BOX 23000</AddrLine2>
|
||||||
|
<City>HICKORY</City>
|
||||||
|
<StateProvince>NC</StateProvince>
|
||||||
|
<PostalCode>28603</PostalCode>
|
||||||
|
<CountryCode>USA</CountryCode>
|
||||||
|
<Contacts/>
|
||||||
|
</Address>
|
||||||
|
</BillTo>
|
||||||
|
</Payment>
|
||||||
|
<PriceSheets>
|
||||||
|
<PriceSheet type="Carrier" isSelected="false"> // get this from the price sheet
|
||||||
|
<ContractId/>
|
||||||
|
<SCAC/>
|
||||||
|
<Mode/>
|
||||||
|
</PriceSheet>
|
||||||
|
</PriceSheets>
|
||||||
|
<Plan>
|
||||||
|
<Events count="2">
|
||||||
|
<Event type="Pickup" sequenceNum="1">
|
||||||
|
<Dates>
|
||||||
|
<Date type="earliest">[loadingDate]</Date>
|
||||||
|
<Date type="latest">[deliveryDate]</Date>
|
||||||
|
</Dates>
|
||||||
|
<Address type="" isResidential="" isPrimary="false">
|
||||||
|
<LocationCode/>
|
||||||
|
<Name>[plantName]</Name>
|
||||||
|
<AddrLine1>[plantStreetAddress]</AddrLine1>
|
||||||
|
<AddrLine2/>
|
||||||
|
<City>[plantCity]</City>
|
||||||
|
<StateProvince>[plantState]</StateProvince>
|
||||||
|
<PostalCode>[plantZipCode]</PostalCode>
|
||||||
|
<CountryCode>USA</CountryCode>
|
||||||
|
<Contacts>
|
||||||
|
<Contact type="">
|
||||||
|
<Name/>
|
||||||
|
<ContactMethods>
|
||||||
|
<ContactMethod sequenceNum="1" type="phone">[contactNum]</ContactMethod>
|
||||||
|
<ContactMethod sequenceNum="1" type="email">[contactEmail]</ContactMethod>
|
||||||
|
</ContactMethods>
|
||||||
|
</Contact>
|
||||||
|
</Contacts>
|
||||||
|
</Address>
|
||||||
|
<Shipments>
|
||||||
|
<ReferenceNumbers>
|
||||||
|
<ReferenceNumber type="Shipment Number" isPrimary="true">[shipNumber]</ReferenceNumber>
|
||||||
|
</ReferenceNumbers>
|
||||||
|
</Shipments>
|
||||||
|
</Event>
|
||||||
|
<Event type="Drop" sequenceNum="2">
|
||||||
|
<Dates>
|
||||||
|
<Date type="earliest">[loadingDate]</Date>
|
||||||
|
<Date type="latest">[deliveryDate]</Date>
|
||||||
|
</Dates>
|
||||||
|
<Address type="" isResidential="" isPrimary="false">
|
||||||
|
<LocationCode/>
|
||||||
|
<Name>[customerName]</Name>
|
||||||
|
<AddrLine1>[customerStreetAddress]</AddrLine1>
|
||||||
|
<AddrLine2/>
|
||||||
|
<City>[customerCity]</City>
|
||||||
|
<StateProvince>[customerState]</StateProvince>
|
||||||
|
<PostalCode>[customerZip]</PostalCode>
|
||||||
|
<CountryCode>USA</CountryCode>
|
||||||
|
<Contacts>
|
||||||
|
<Contact type="">
|
||||||
|
<Name/>
|
||||||
|
<ContactMethods>
|
||||||
|
<ContactMethod sequenceNum="1" type="phone">800-555-1122</ContactMethod>
|
||||||
|
</ContactMethods>
|
||||||
|
</Contact>
|
||||||
|
</Contacts>
|
||||||
|
</Address>
|
||||||
|
<Shipments>
|
||||||
|
<ReferenceNumbers>
|
||||||
|
<ReferenceNumber type="Shipment Number" isPrimary="true">[shipNumber]</ReferenceNumber>
|
||||||
|
</ReferenceNumbers>
|
||||||
|
</Shipments>
|
||||||
|
</Event>
|
||||||
|
|
||||||
|
</Events>
|
||||||
|
</Plan>
|
||||||
|
<Shipments>
|
||||||
|
<Shipment type="Regular" action="UpdateOrAdd">
|
||||||
|
<Status>Pending</Status>
|
||||||
|
<Enterprise name="" customerAcctNum="[customerAccountNum]"/>
|
||||||
|
<ReferenceNumbers>
|
||||||
|
<ReferenceNumber type="Shipment Number" isPrimary="true">[shipNumber]</ReferenceNumber>
|
||||||
|
<ReferenceNumber type="PO Number" isPrimary="false">[customerPO]</ReferenceNumber>
|
||||||
|
[multieReleaseNumber]
|
||||||
|
<ReferenceNumber type="Store Number" isPrimary="false">[glCoding]</ReferenceNumber>
|
||||||
|
<ReferenceNumber type="Profit Center" isPrimary="false">[pfc]</ReferenceNumber>
|
||||||
|
</ReferenceNumbers>
|
||||||
|
<Services/>
|
||||||
|
<EquipmentList/>
|
||||||
|
6
|
||||||
|
<Dates>
|
||||||
|
<Pickup>
|
||||||
|
<Date type="earliest">[loadingDate]</Date>
|
||||||
|
<Date type="latest">[loadingDate]</Date>
|
||||||
|
</Pickup>
|
||||||
|
<Drop>
|
||||||
|
<Date type="earliest">[deliveryDate]</Date>
|
||||||
|
<Date type="latest">[deliveryDate]</Date>
|
||||||
|
</Drop>
|
||||||
|
</Dates>
|
||||||
|
<PriceSheets>
|
||||||
|
<PriceSheet type="Carrier" isSelected="false">
|
||||||
|
<ContractId/>
|
||||||
|
<SCAC/>
|
||||||
|
<Mode/>
|
||||||
|
</PriceSheet>
|
||||||
|
</PriceSheets>
|
||||||
|
<Shipper>
|
||||||
|
<Address type="" isResidential="" isPrimary="false">
|
||||||
|
<LocationCode/>
|
||||||
|
<Name>[plantName]</Name>
|
||||||
|
<AddrLine1>[plantStreetAddress]</AddrLine1>
|
||||||
|
<AddrLine2/>
|
||||||
|
<City>[plantCity]</City>
|
||||||
|
<StateProvince>[plantState]</StateProvince>
|
||||||
|
<PostalCode>[plantZipCode]</PostalCode>
|
||||||
|
<CountryCode>USA</CountryCode>
|
||||||
|
<Contacts>
|
||||||
|
<Contact type="">
|
||||||
|
<Name/>
|
||||||
|
<ContactMethods>
|
||||||
|
<ContactMethod sequenceNum="1" type="phone">[contactNum]</ContactMethod>
|
||||||
|
</ContactMethods>
|
||||||
|
</Contact>
|
||||||
|
</Contacts>
|
||||||
|
</Address>
|
||||||
|
</Shipper>
|
||||||
|
<Consignee>
|
||||||
|
<Address type="" isResidential="" isPrimary="false">
|
||||||
|
<LocationCode/>
|
||||||
|
<Name>[customer]</Name>
|
||||||
|
<AddrLine1>[customerStreetAddress]</AddrLine1>
|
||||||
|
<AddrLine2/>
|
||||||
|
<City>[customerCity]</City>
|
||||||
|
<StateProvince>[customerState]</StateProvince>
|
||||||
|
<PostalCode>[customerZip]</PostalCode>
|
||||||
|
<CountryCode>USA</CountryCode>
|
||||||
|
<Contacts />
|
||||||
|
|
||||||
|
</Address>
|
||||||
|
</Consignee>
|
||||||
|
<ItemGroups>
|
||||||
|
[items]
|
||||||
|
</ItemGroups>
|
||||||
|
<Payment>
|
||||||
|
<Method>Prepaid</Method>
|
||||||
|
<BillTo thirdParty="False">
|
||||||
|
<Address Type="BillTo" isResidential="False">
|
||||||
|
<Alias/>
|
||||||
|
<Name>ALPLA</Name>
|
||||||
|
<AddrLine1>CO TRANSPORTATION INSIGHT</AddrLine1>
|
||||||
|
<AddrLine2>PO BOX 23000</AddrLine2>
|
||||||
|
<City>HICKORY</City>
|
||||||
|
<StateProvince>NC</StateProvince>
|
||||||
|
<PostalCode>28603</PostalCode>
|
||||||
|
<CountryCode>USA</CountryCode>
|
||||||
|
<Contacts/>
|
||||||
|
</Address>
|
||||||
|
</BillTo>
|
||||||
|
</Payment>
|
||||||
|
</Shipment>
|
||||||
|
</Shipments>
|
||||||
|
</Load>
|
||||||
|
</MercuryGate>
|
||||||
|
</WebImportFile>
|
||||||
|
</WebImport>
|
||||||
|
</data>
|
||||||
|
</service-request>
|
||||||
|
`;
|
||||||
@@ -0,0 +1,424 @@
|
|||||||
|
import { xmlPayloadTI } from "./tiFullFlow/tiXmlPayload.js";
|
||||||
|
import axios from "axios";
|
||||||
|
import querystring from "querystring";
|
||||||
|
import { getOrderToSend } from "../../../sqlServer/querys/notifications/ti/getOrderToSend.js";
|
||||||
|
import { getHeaders } from "../../../sqlServer/querys/notifications/ti/getHeaders.js";
|
||||||
|
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
|
||||||
|
import { db } from "../../../../../database/dbclient.js";
|
||||||
|
import { settings } from "../../../../../database/schema/settings.js";
|
||||||
|
import { serverData } from "../../../../../database/schema/serverData.js";
|
||||||
|
import { eq, sql } from "drizzle-orm";
|
||||||
|
import { notifications } from "../../../../../database/schema/notifications.js";
|
||||||
|
import { query } from "../../../sqlServer/prodSqlServer.js";
|
||||||
|
import { createLog } from "../../../logger/logger.js";
|
||||||
|
import { freightClass } from "../../../../globalUtils/freightClass.js";
|
||||||
|
import { delay } from "../../../../globalUtils/delay.js";
|
||||||
|
|
||||||
|
const dateCorrection = (newDate: any) => {
|
||||||
|
return new Date(newDate)
|
||||||
|
.toLocaleString("en-US", {
|
||||||
|
timeZone: "UTC",
|
||||||
|
year: "numeric",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
second: "2-digit",
|
||||||
|
hourCycle: "h23", // Ensures 24-hour format
|
||||||
|
})
|
||||||
|
.replace(",", "");
|
||||||
|
};
|
||||||
|
|
||||||
|
const tiImport = async () => {
|
||||||
|
//await initializePool();
|
||||||
|
|
||||||
|
// get the plant token
|
||||||
|
const { data: plantData, error: plantError } = await tryCatch(
|
||||||
|
db.select().from(settings)
|
||||||
|
);
|
||||||
|
//await initializePool();
|
||||||
|
if (plantError) return;
|
||||||
|
const plantToken = plantData?.filter((n) => n.name === "plantToken");
|
||||||
|
|
||||||
|
const { data: plantInfo, error: plantEr } = await tryCatch(
|
||||||
|
db
|
||||||
|
.select()
|
||||||
|
.from(serverData)
|
||||||
|
.where(eq(serverData.plantToken, plantToken[0].value))
|
||||||
|
);
|
||||||
|
|
||||||
|
// parsing posting window
|
||||||
|
const plantI = plantInfo!;
|
||||||
|
const postTime = JSON.parse(plantI[0]?.tiPostTime!);
|
||||||
|
|
||||||
|
// order notifications
|
||||||
|
const { data: notificationSet, error: notificationSettingsErr } =
|
||||||
|
await tryCatch(
|
||||||
|
db
|
||||||
|
.select()
|
||||||
|
.from(notifications)
|
||||||
|
.where(eq(notifications.name, "tiIntergration"))
|
||||||
|
);
|
||||||
|
if (notificationSettingsErr) return;
|
||||||
|
|
||||||
|
const notiSet: any = notificationSet;
|
||||||
|
//creds
|
||||||
|
const userid = "ALPLAWSTEST";
|
||||||
|
const password = "oe39U1LuLX9ZdY0XKobG";
|
||||||
|
|
||||||
|
// const requestID = `ALPLAPBTEST1`; // production will be alpla01-dateTime - this will be the time it was sent over.
|
||||||
|
const requestUser = "ALPLAWSTEST"; // if alplaprod_rs -- confirm we can use a user name vs the AlplapIMPORT // needs to stay the same as provied
|
||||||
|
|
||||||
|
const customerAccountNum = plantI[0].customerTiAcc as string; // ti
|
||||||
|
|
||||||
|
// it we dont get anything here we want to make sure we add it in
|
||||||
|
|
||||||
|
// get current releaes not in the already sent oders
|
||||||
|
let orders = getHeaders;
|
||||||
|
orders = orders
|
||||||
|
.replaceAll("test1", plantToken[0].value)
|
||||||
|
.replaceAll("[from]", notiSet?.notifiySettings.start)
|
||||||
|
.replaceAll("[to]", notiSet?.notifiySettings.end)
|
||||||
|
.replaceAll(
|
||||||
|
"[exclude]",
|
||||||
|
notiSet.notifiySettings.processed
|
||||||
|
.map((num: any) => `'${num}'`)
|
||||||
|
.join(", ")
|
||||||
|
);
|
||||||
|
|
||||||
|
//console.log(orders);
|
||||||
|
let headerPending = [];
|
||||||
|
try {
|
||||||
|
headerPending = await query(orders, "Ti get open headers");
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headerPending.length === 0) {
|
||||||
|
createLog(
|
||||||
|
"info",
|
||||||
|
"notification",
|
||||||
|
"notify",
|
||||||
|
"There are no pending orders to be sent over to ti."
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
code: 1,
|
||||||
|
message: "There are no pending orders to be sent over to ti.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
createLog(
|
||||||
|
"info",
|
||||||
|
"notification",
|
||||||
|
"notify",
|
||||||
|
`There are a total of ${headerPending.length} to send over`
|
||||||
|
);
|
||||||
|
// update query to have the correct plant token
|
||||||
|
let orderToSend = getOrderToSend;
|
||||||
|
orderToSend = orderToSend
|
||||||
|
.replaceAll("test1", plantToken[0].value)
|
||||||
|
.replaceAll("[releaseToProcess]", `'${headerPending[0].releaseNumber}'`)
|
||||||
|
.replaceAll("[from]", notiSet.notifiySettings.start)
|
||||||
|
.replaceAll("[to]", notiSet.notifiySettings.end);
|
||||||
|
|
||||||
|
// console.log(orderToSend);
|
||||||
|
let records = [];
|
||||||
|
try {
|
||||||
|
records = await query(orderToSend, "Ti send order");
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
//console.log(headerPending.length);
|
||||||
|
|
||||||
|
// update the header
|
||||||
|
let webHeader = `
|
||||||
|
<request-id>[requestID]</request-id>
|
||||||
|
<data>
|
||||||
|
<WebImport>
|
||||||
|
<WebImportHeader>
|
||||||
|
<FileName>[requestID].XML</FileName>
|
||||||
|
<Type>SOTransportLoader</Type>
|
||||||
|
<UserName>[requestUser]</UserName>
|
||||||
|
</WebImportHeader>
|
||||||
|
`;
|
||||||
|
|
||||||
|
webHeader = webHeader.replaceAll(
|
||||||
|
"[requestID]",
|
||||||
|
`${records[0].releaseNumber}-${plantToken[0].value}`
|
||||||
|
);
|
||||||
|
webHeader = webHeader.replaceAll("[requestUser]", requestUser);
|
||||||
|
|
||||||
|
// this part will link into the <ItemGroups></ItemGroups>
|
||||||
|
let itemGroups = "";
|
||||||
|
|
||||||
|
for (let i = 0; i < records.length; i++) {
|
||||||
|
let newItem = `
|
||||||
|
<ItemGroup id="" isShipUnit="false" isHandlingUnit="false" sequence="${
|
||||||
|
i + 1
|
||||||
|
}">
|
||||||
|
<ContainedBy id=""/>
|
||||||
|
<LineItem lineNumber="${i + 1}"/>
|
||||||
|
<Dimensions>
|
||||||
|
<Dimension type="Length" uom="IN">${(
|
||||||
|
records[i].pkgLengh / 25.4
|
||||||
|
).toFixed(2)}</Dimension>
|
||||||
|
<Dimension type="Width" uom="IN">${(
|
||||||
|
records[i].pkgWidth / 25.4
|
||||||
|
).toFixed(2)}</Dimension>
|
||||||
|
<Dimension type="Height" uom="IN">${Math.round(
|
||||||
|
records[i].pkgHeight / 25.4
|
||||||
|
).toFixed(2)}</Dimension>
|
||||||
|
</Dimensions>
|
||||||
|
<Description>${`av ${records[i].article} ${records[i].articleAlias}`}</Description>
|
||||||
|
<FreightClasses>
|
||||||
|
<FreightClass type="">${freightClass(
|
||||||
|
records[i].pkgWeight,
|
||||||
|
records[i].pkgLengh,
|
||||||
|
records[i].pkgWidth,
|
||||||
|
records[i].pkgHeight
|
||||||
|
)}</FreightClass>
|
||||||
|
</FreightClasses>
|
||||||
|
<Commodity/>
|
||||||
|
<NmfcCode/>
|
||||||
|
<HazardousMaterial>false</HazardousMaterial>
|
||||||
|
<HazMatDetail/>
|
||||||
|
<Weights>
|
||||||
|
<Weight type="actual" uom="KG">${
|
||||||
|
records[i].pkgWeight * records[i].Pallets
|
||||||
|
}</Weight>
|
||||||
|
</Weights>
|
||||||
|
<Quantities>
|
||||||
|
<Quantity type="actual" uom="pallet">${
|
||||||
|
records[i].Pallets
|
||||||
|
}</Quantity>
|
||||||
|
</Quantities>
|
||||||
|
</ItemGroup>
|
||||||
|
`;
|
||||||
|
|
||||||
|
itemGroups += newItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the full amount of pallets sending over
|
||||||
|
let fullPalToSend = records.reduce(
|
||||||
|
(acc: any, o: any) => acc + o.Pallets,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
// rebuild the xml to be properly
|
||||||
|
let payload = xmlPayloadTI;
|
||||||
|
payload = payload
|
||||||
|
.replaceAll(`[WebImportHeader]`, webHeader)
|
||||||
|
.replaceAll(`[items]`, itemGroups)
|
||||||
|
.replaceAll(`[customerAccountNum]`, customerAccountNum)
|
||||||
|
.replaceAll("[fullTotalPal]", fullPalToSend);
|
||||||
|
|
||||||
|
// update the main release
|
||||||
|
//[loadNumber],[shipNumber]
|
||||||
|
payload = payload.replaceAll(`[shipNumber]`, records[0].releaseNumber);
|
||||||
|
payload = payload.replaceAll(`[loadNumber]`, records[0].releaseNumber);
|
||||||
|
|
||||||
|
// do the multie release if needed
|
||||||
|
// <ReferenceNumber type="Release Number" isPrimary="false">[multieReleaseNumber]</ReferenceNumber>
|
||||||
|
|
||||||
|
let multiRelease = ``;
|
||||||
|
if (records.length > 0) {
|
||||||
|
for (let i = 0; i < records.length; i++) {
|
||||||
|
const newRelease = `
|
||||||
|
<ReferenceNumber type="Release Number" isPrimary="false">${records[i].releaseNumber}</ReferenceNumber>`;
|
||||||
|
multiRelease += newRelease;
|
||||||
|
}
|
||||||
|
|
||||||
|
payload = payload.replaceAll("[multieReleaseNumber]", multiRelease);
|
||||||
|
} else {
|
||||||
|
payload = payload.replaceAll("[multieReleaseNumber]", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
//update the delivery section
|
||||||
|
payload = payload.replaceAll(
|
||||||
|
"[loadingDate]",
|
||||||
|
dateCorrection(records[0].LoadingDate)
|
||||||
|
);
|
||||||
|
|
||||||
|
payload = payload.replaceAll(
|
||||||
|
"[deliveryDate]",
|
||||||
|
dateCorrection(records[0].DeliveryDate)
|
||||||
|
);
|
||||||
|
|
||||||
|
// shipping hours
|
||||||
|
//<Date type="earliest">[shippingHoursEarly]</Date>
|
||||||
|
//<Date type="latest">[shippingHoursLate]</Date>
|
||||||
|
|
||||||
|
// update teh shipping hours
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const formattedDate = records[0].LoadingDate.toLocaleDateString("en-US", {
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
|
year: "numeric",
|
||||||
|
});
|
||||||
|
|
||||||
|
const shippingHours = JSON.parse(plantI[0]?.shippingHours!);
|
||||||
|
//console.log(shippingHours);
|
||||||
|
|
||||||
|
payload = payload
|
||||||
|
.replaceAll(
|
||||||
|
"[shippingHoursEarly]",
|
||||||
|
`${formattedDate} ${shippingHours[0].early}`
|
||||||
|
)
|
||||||
|
.replaceAll(
|
||||||
|
"[shippingHoursLate]",
|
||||||
|
`${formattedDate} ${shippingHours[0].late}`
|
||||||
|
);
|
||||||
|
|
||||||
|
payload = payload
|
||||||
|
.replaceAll("[plantName]", `Alpla ${plantI[0]?.sName!}`)
|
||||||
|
.replaceAll("[plantStreetAddress]", plantI[0]?.streetAddress!)
|
||||||
|
.replaceAll("[plantCity]", plantI[0]?.cityState!.split(",")[0])
|
||||||
|
.replaceAll("[plantState]", plantI[0]?.cityState!.split(",")[1])
|
||||||
|
.replaceAll("[plantZipCode]", plantI[0]?.zipcode!)
|
||||||
|
.replaceAll("[contactNum]", plantI[0]?.contactPhone!)
|
||||||
|
.replaceAll("[contactEmail]", plantI[0]?.contactEmail!)
|
||||||
|
|
||||||
|
// customer info
|
||||||
|
.replaceAll("[customerName]", records[0].addressAlias)
|
||||||
|
.replaceAll("[customerStreetAddress]", records[0].streetAddress)
|
||||||
|
.replaceAll("[customerCity]", records[0].city.split(",")[0])
|
||||||
|
.replaceAll("[customerState]", records[0].city.split(",")[1])
|
||||||
|
.replaceAll("[customerZip]", records[0].zipCode)
|
||||||
|
.replaceAll("[customerPO]", records[0].Header)
|
||||||
|
.replaceAll(
|
||||||
|
"[glCoding]",
|
||||||
|
`52410-${
|
||||||
|
records[0].artileType.toLowerCase() === "preform" ||
|
||||||
|
records[0].artileType.toLowerCase() === "metalCage"
|
||||||
|
? 31
|
||||||
|
: plantI[0].greatPlainsPlantCode
|
||||||
|
}`
|
||||||
|
) // {"52410 - " + (artileType.toLowerCase() === "preform" || artileType.toLowerCase() === "metalCage" ? 31: plantInfo[0].greatPlainsPlantCode)}
|
||||||
|
.replaceAll(
|
||||||
|
"[pfc]",
|
||||||
|
`${
|
||||||
|
records[0].artileType.toLowerCase() === "preform" ||
|
||||||
|
records[0].artileType.toLowerCase() === "metalCage"
|
||||||
|
? 40
|
||||||
|
: records[0].costCenter
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
|
||||||
|
// special instructions
|
||||||
|
.replaceAll(
|
||||||
|
"[specialInstructions]",
|
||||||
|
`This is a FTL load. The driver will need 2 adjustable load locks to secure the load. The driver will not be loaded without them. Please reference ALPLA pickup ${records[0].Header}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// update the carrier info if any is needed.
|
||||||
|
|
||||||
|
// check the address has a real carrier on it and change to true and put the sacs code in
|
||||||
|
const hasCarrier = true;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Checking if ${records[0].addressAlias} has scac: ${
|
||||||
|
records[0].remark.split(",")[0] ? "there was one" : "no scac"
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const priceSheet = `
|
||||||
|
<PriceSheets>
|
||||||
|
<PriceSheet type="Carrier" isSelected="${
|
||||||
|
records[0].remark.split(",")[0] ? "true" : "false"
|
||||||
|
}">
|
||||||
|
<ContractId/>
|
||||||
|
${
|
||||||
|
records[0].remark.split(",")[0]
|
||||||
|
? `<SCAC>${records[0].remark
|
||||||
|
.split(",")[0]
|
||||||
|
.split(":")[1]
|
||||||
|
.toUpperCase()}</SCAC>`
|
||||||
|
: `<SCAC/>`
|
||||||
|
}
|
||||||
|
<Mode/>
|
||||||
|
</PriceSheet>
|
||||||
|
</PriceSheets>
|
||||||
|
`;
|
||||||
|
|
||||||
|
payload = payload.replaceAll("[priceSheet]", priceSheet);
|
||||||
|
// console.log(payload);
|
||||||
|
//await closePool();
|
||||||
|
|
||||||
|
//put the xml into a form
|
||||||
|
const formBody = querystring.stringify({
|
||||||
|
userid,
|
||||||
|
password,
|
||||||
|
request: payload,
|
||||||
|
});
|
||||||
|
axios
|
||||||
|
.post(
|
||||||
|
"https://t-insightws.mercurygate.net/MercuryGate/common/remoteService.jsp",
|
||||||
|
formBody,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
//console.log(response.data)
|
||||||
|
console.log("Data was sent over to TI");
|
||||||
|
})
|
||||||
|
.catch((error) => console.error(error));
|
||||||
|
|
||||||
|
// console.log(payload);
|
||||||
|
|
||||||
|
// the order is done so we want to update the processed.
|
||||||
|
|
||||||
|
// add the new processed order to this
|
||||||
|
let notiSettingArray = notiSet.notifiySettings;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!notiSettingArray[0].processed.includes(headerPending[0].releaseNumber)
|
||||||
|
) {
|
||||||
|
notiSettingArray[0].processed.push(headerPending[0].releaseNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, error } = await tryCatch(
|
||||||
|
db
|
||||||
|
.update(notifications)
|
||||||
|
.set({
|
||||||
|
lastRan: sql`NOW()`,
|
||||||
|
notifiySettings: {
|
||||||
|
...notiSettingArray,
|
||||||
|
prodID: 1,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.where(eq(notifications.name, "tiIntergration"))
|
||||||
|
);
|
||||||
|
createLog("info", "ti", "notify", "done with this order");
|
||||||
|
return { success: true, code: 0, message: "done with this order" };
|
||||||
|
};
|
||||||
|
|
||||||
|
// add a running check so we cant flag it twice
|
||||||
|
export let tiExportRunning = false;
|
||||||
|
export const runTiImport = async () => {
|
||||||
|
let finished = false;
|
||||||
|
let test: any;
|
||||||
|
tiExportRunning = true;
|
||||||
|
do {
|
||||||
|
createLog("info", "ti", "notify", "processing new data");
|
||||||
|
// code block to be executed
|
||||||
|
test = await tiImport();
|
||||||
|
createLog(
|
||||||
|
"info",
|
||||||
|
"ti",
|
||||||
|
"notify",
|
||||||
|
`Still more to process? ${test.code === 1 ? "No" : "Yes"}`
|
||||||
|
);
|
||||||
|
if (test.code === 1) {
|
||||||
|
finished = true;
|
||||||
|
}
|
||||||
|
await delay(1000 * 5);
|
||||||
|
} while (!finished);
|
||||||
|
tiExportRunning = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default tiImport;
|
||||||
@@ -1,9 +1,17 @@
|
|||||||
import { OpenAPIHono } from "@hono/zod-openapi";
|
import { OpenAPIHono } from "@hono/zod-openapi";
|
||||||
|
|
||||||
import sendemail from "./routes/sendMail.js";
|
import sendemail from "./routes/sendMail.js";
|
||||||
|
import { tryCatch } from "../../globalUtils/tryCatch.js";
|
||||||
|
import { db } from "../../../database/dbclient.js";
|
||||||
|
|
||||||
|
import { notifications } from "../../../database/schema/notifications.js";
|
||||||
|
import { createLog } from "../logger/logger.js";
|
||||||
|
import { note, notificationCreate } from "./utils/masterNotifications.js";
|
||||||
|
import { startNotificationMonitor } from "./utils/processNotifications.js";
|
||||||
|
import notifyStats from "./routes/getActiveNotifications.js";
|
||||||
const app = new OpenAPIHono();
|
const app = new OpenAPIHono();
|
||||||
|
|
||||||
const routes = [sendemail] as const;
|
const routes = [sendemail, notifyStats] as const;
|
||||||
|
|
||||||
const appRoutes = routes.forEach((route) => {
|
const appRoutes = routes.forEach((route) => {
|
||||||
app.route("/notify", route);
|
app.route("/notify", route);
|
||||||
@@ -16,4 +24,37 @@ app.all("/notify/*", (c) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// check if the mastNotications is changed compared to the db and add if needed.
|
||||||
|
const { data: notes, error: notesError } = await tryCatch(
|
||||||
|
db.select().from(notifications)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (notesError) {
|
||||||
|
createLog(
|
||||||
|
"error",
|
||||||
|
"notify",
|
||||||
|
"notify",
|
||||||
|
`There was an error getting the notifications: ${JSON.stringify(
|
||||||
|
notesError
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (note.length != notes?.length) {
|
||||||
|
notificationCreate();
|
||||||
|
createLog("info", "notify", "notify", `New notifcations being added.`);
|
||||||
|
setTimeout(() => {
|
||||||
|
startNotificationMonitor();
|
||||||
|
}, 5 * 1000);
|
||||||
|
} else {
|
||||||
|
createLog(
|
||||||
|
"info",
|
||||||
|
"notify",
|
||||||
|
"notify",
|
||||||
|
`There are know new notifcations. no need to run the update. reminder all changes happen per server.`
|
||||||
|
);
|
||||||
|
setTimeout(() => {
|
||||||
|
startNotificationMonitor();
|
||||||
|
}, 5 * 1000);
|
||||||
|
}
|
||||||
export default app;
|
export default app;
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// an external way to creating logs
|
||||||
|
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
|
||||||
|
import { responses } from "../../../globalUtils/routeDefs/responses.js";
|
||||||
|
import { getAllJobs } from "../utils/processNotifications.js";
|
||||||
|
|
||||||
|
const app = new OpenAPIHono({ strict: false });
|
||||||
|
|
||||||
|
app.openapi(
|
||||||
|
createRoute({
|
||||||
|
tags: ["server"],
|
||||||
|
summary: "Returns current active notifications.",
|
||||||
|
method: "get",
|
||||||
|
path: "/activenotifications",
|
||||||
|
//middleware: authMiddleware,
|
||||||
|
responses: responses(),
|
||||||
|
}),
|
||||||
|
async (c) => {
|
||||||
|
const jobs = getAllJobs();
|
||||||
|
return c.json({
|
||||||
|
success: true,
|
||||||
|
message:
|
||||||
|
jobs.length === 0
|
||||||
|
? "There are no active Notifications Currently."
|
||||||
|
: "Current Active notifications",
|
||||||
|
data: jobs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
export default app;
|
||||||
22
server/services/notifications/routes/manualTiggerTi.ts
Normal file
22
server/services/notifications/routes/manualTiggerTi.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// import {Router} from "express";
|
||||||
|
// import {tiExportRunning, runTiImport} from "../../notification/notification/tiFullFlow/tiImports.js";
|
||||||
|
|
||||||
|
// const router = Router();
|
||||||
|
|
||||||
|
// router.get("/tiTrigger", async (req, res): Promise<void> => {
|
||||||
|
// if (tiExportRunning) {
|
||||||
|
// res.status(200).json({
|
||||||
|
// success: false,
|
||||||
|
// message: "There is already a current sesion of the Export running please try again later.",
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // trigger the import
|
||||||
|
// runTiImport();
|
||||||
|
|
||||||
|
// res.status(200).json({
|
||||||
|
// success: true,
|
||||||
|
// message: "The Ti Export has been manually started and will continue to run in the background.",
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// export default router;
|
||||||
113
server/services/notifications/utils/masterNotifications.ts
Normal file
113
server/services/notifications/utils/masterNotifications.ts
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import { db } from "../../../../database/dbclient.js";
|
||||||
|
import { notifications } from "../../../../database/schema/notifications.js";
|
||||||
|
import { createLog } from "../../logger/logger.js";
|
||||||
|
|
||||||
|
export const note: any = [
|
||||||
|
{
|
||||||
|
name: "reprintLabels",
|
||||||
|
description:
|
||||||
|
"Monitors the labels that are printed and returns a value if one falls withing the time frame defined below.",
|
||||||
|
checkInterval: 1,
|
||||||
|
timeType: "min",
|
||||||
|
emails: "",
|
||||||
|
active: false,
|
||||||
|
notifiySettings: { prodID: 1 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "downTimeCheck",
|
||||||
|
description:
|
||||||
|
"Checks for specific downtimes that are greater than 105 min.",
|
||||||
|
checkInterval: 30,
|
||||||
|
timeType: "min",
|
||||||
|
emails: "",
|
||||||
|
active: false,
|
||||||
|
notifiySettings: { prodID: 1, daysInPast: 5, duration: 105 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "qualityBlocking",
|
||||||
|
description:
|
||||||
|
"Checks for new blocking orders that have been entered, recommened to get the most recent order in here before activating.",
|
||||||
|
checkInterval: 30,
|
||||||
|
timeType: "min",
|
||||||
|
emails: "",
|
||||||
|
active: false,
|
||||||
|
notifiySettings: { prodID: 1, sentBlockingOrders: [1] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "productionCheck",
|
||||||
|
description: "Checks ppoo",
|
||||||
|
checkInterval: 2,
|
||||||
|
timeType: "hour",
|
||||||
|
emails: "",
|
||||||
|
active: false,
|
||||||
|
notifiySettings: {
|
||||||
|
prodID: 1,
|
||||||
|
count: 0,
|
||||||
|
weekend: false,
|
||||||
|
locations: "0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stagingCheck",
|
||||||
|
description:
|
||||||
|
"Checks staging based on locations, locations need to be seperated by a ,",
|
||||||
|
checkInterval: 2,
|
||||||
|
timeType: "hour",
|
||||||
|
emails: "",
|
||||||
|
active: false,
|
||||||
|
notifiySettings: {
|
||||||
|
prodID: 1,
|
||||||
|
count: 0,
|
||||||
|
weekend: false,
|
||||||
|
locations: "0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tiIntergration",
|
||||||
|
description: "Checks for new releases to be put into ti",
|
||||||
|
checkInterval: 2,
|
||||||
|
timeType: "hour",
|
||||||
|
emails: "",
|
||||||
|
active: false,
|
||||||
|
notifiySettings: {
|
||||||
|
prodID: 1,
|
||||||
|
start: 36,
|
||||||
|
end: 720,
|
||||||
|
releases: [1, 2, 3],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exampleNotification",
|
||||||
|
description: "Checks for new releases to be put into ti",
|
||||||
|
checkInterval: 2,
|
||||||
|
timeType: "min",
|
||||||
|
emails: "",
|
||||||
|
active: true,
|
||||||
|
notifiySettings: {
|
||||||
|
prodID: 1,
|
||||||
|
start: 36,
|
||||||
|
end: 720,
|
||||||
|
releases: [1, 2, 3],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const notificationCreate = async () => {
|
||||||
|
for (let i = 0; i < note.length; i++) {
|
||||||
|
try {
|
||||||
|
const notify = await db
|
||||||
|
.insert(notifications)
|
||||||
|
.values(note[i])
|
||||||
|
.onConflictDoNothing();
|
||||||
|
} catch (error) {
|
||||||
|
createLog(
|
||||||
|
"error",
|
||||||
|
"notify",
|
||||||
|
"notify",
|
||||||
|
`There was an error getting the notifications: ${JSON.stringify(
|
||||||
|
error
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
158
server/services/notifications/utils/processNotifications.ts
Normal file
158
server/services/notifications/utils/processNotifications.ts
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
import { db } from "../../../../database/dbclient.js";
|
||||||
|
import { notifications } from "../../../../database/schema/notifications.js";
|
||||||
|
import { tryCatch } from "../../../globalUtils/tryCatch.js";
|
||||||
|
import { createLog } from "../../logger/logger.js";
|
||||||
|
import { Cron } from "croner";
|
||||||
|
|
||||||
|
// Store active timeouts by notification ID
|
||||||
|
export let runningNotifications: Record<string, Cron> = {};
|
||||||
|
|
||||||
|
export const startNotificationMonitor = async () => {
|
||||||
|
// if restarted or crashed we need to make sure the running notifications is cleared
|
||||||
|
createLog("info", "notify", "notify", `Notification system is now active.`);
|
||||||
|
|
||||||
|
setInterval(async () => {
|
||||||
|
const { data, error } = await tryCatch(db.select().from(notifications));
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
createLog(
|
||||||
|
"error",
|
||||||
|
"notify",
|
||||||
|
"notify",
|
||||||
|
`There was an error getting the notifications: ${JSON.stringify(
|
||||||
|
error
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const notes: any = data;
|
||||||
|
|
||||||
|
for (const note of notes) {
|
||||||
|
//if we get deactivated remove it.
|
||||||
|
if (runningNotifications[note.name] && !note.active) {
|
||||||
|
createLog(
|
||||||
|
"info",
|
||||||
|
"notify",
|
||||||
|
"notify",
|
||||||
|
`${note.name} was just deactivated`
|
||||||
|
);
|
||||||
|
removeNotification(note.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we are not active, no emails, and already in place just stop.
|
||||||
|
|
||||||
|
if (
|
||||||
|
!note.active ||
|
||||||
|
note.emails === "" ||
|
||||||
|
runningNotifications[note.name]
|
||||||
|
) {
|
||||||
|
//console.log(`Skipping ${note.name} hes already scheduled`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let time = `*/30 * * * *`; // default to be every 30 min
|
||||||
|
|
||||||
|
if (note.timeType === "min") {
|
||||||
|
console.log(`Creating the min mark here`);
|
||||||
|
time = `*/${note.checkInterval} * * * *`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (note.timeType === "hour") {
|
||||||
|
console.log(`Creating the hour mark here`);
|
||||||
|
time = `* */${note.checkInterval} * * *`;
|
||||||
|
}
|
||||||
|
|
||||||
|
createJob(note.name, time, async () => {
|
||||||
|
try {
|
||||||
|
const { default: runFun } = await import(
|
||||||
|
`../controller/notifications/${note.name}.js`
|
||||||
|
);
|
||||||
|
await runFun(note);
|
||||||
|
} catch (error: any) {
|
||||||
|
createLog(
|
||||||
|
"error",
|
||||||
|
"notify",
|
||||||
|
note.name,
|
||||||
|
`Error running notification: ${error.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//testParse(runningNotifcations[note.name]);
|
||||||
|
}
|
||||||
|
}, 5 * 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createJob = (id: string, schedule: string, task: () => Promise<void>) => {
|
||||||
|
// Destroy existing job if it exists
|
||||||
|
if (runningNotifications[id]) {
|
||||||
|
runningNotifications[id].stop(); // Croner uses .stop() instead of .destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new job with Croner
|
||||||
|
runningNotifications[id] = new Cron(
|
||||||
|
schedule,
|
||||||
|
{
|
||||||
|
timezone: "America/Chicago",
|
||||||
|
catch: true, // Prevents unhandled rejections
|
||||||
|
},
|
||||||
|
task
|
||||||
|
);
|
||||||
|
|
||||||
|
// Optional: Add error handling (Croner emits 'error' events)
|
||||||
|
// runningNotifications[id].on("error", (err) => {
|
||||||
|
// console.error(`Job ${id} failed:`, err);
|
||||||
|
// });
|
||||||
|
};
|
||||||
|
|
||||||
|
interface JobInfo {
|
||||||
|
id: string;
|
||||||
|
schedule: string;
|
||||||
|
nextRun: Date | null;
|
||||||
|
isRunning: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAllJobs = (): JobInfo[] => {
|
||||||
|
return Object.entries(runningNotifications).map(([id, job]) => ({
|
||||||
|
id,
|
||||||
|
schedule: job.getPattern() || "invalid",
|
||||||
|
nextRun: job.nextRun() || null,
|
||||||
|
lastRun: job.previousRun() || null,
|
||||||
|
isRunning: job ? !job.isStopped() : false,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeNotification = (id: any) => {
|
||||||
|
if (runningNotifications[id]) {
|
||||||
|
runningNotifications[id].stop();
|
||||||
|
delete runningNotifications[id];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stopAllJobs = () => {
|
||||||
|
Object.values(runningNotifications).forEach((job: any) => job.stop());
|
||||||
|
runningNotifications = {}; // Clear the object
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Pause a job
|
||||||
|
app.post("/api/jobs/:id/pause", (req, res) => {
|
||||||
|
runningNotifications[req.params.id]?.pause();
|
||||||
|
res.json({ success: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Resume a job
|
||||||
|
app.post("/api/jobs/:id/resume", (req, res) => {
|
||||||
|
runningNotifications[req.params.id]?.resume();
|
||||||
|
res.json({ success: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete a job
|
||||||
|
app.delete("/api/jobs/:id", (req, res) => {
|
||||||
|
runningNotifications[req.params.id]?.stop();
|
||||||
|
delete runningNotifications[req.params.id];
|
||||||
|
res.json({ success: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
46
server/services/notifications/utils/views/downTimeCheck.hbs
Normal file
46
server/services/notifications/utils/views/downTimeCheck.hbs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
{{!-- <link rel="stylesheet" href="styles/styles.css" /> --}}
|
||||||
|
{{> styles}}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>All,</p>
|
||||||
|
<p>The below downtimes have exceeded the max requested limit of {{secondarySetting.duration}}min</p>
|
||||||
|
<table >
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>totalDuration</th>
|
||||||
|
<th>machineAlias</th>
|
||||||
|
<th>CTO_Code</th>
|
||||||
|
<th>Downtime_Description</th>
|
||||||
|
<th>groupDesc</th>
|
||||||
|
<th>remark</th>
|
||||||
|
<th>Downtime start</th>
|
||||||
|
<th>Downtime finish</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{#each items}}
|
||||||
|
<tr>
|
||||||
|
<td>{{totalDuration}}</td>
|
||||||
|
<td>{{machineAlias}}</td>
|
||||||
|
<td>{{CTO_Code}}</td>
|
||||||
|
<td>{{Downtime_Description}}</td>
|
||||||
|
<td>{{groupDesc}}</td>
|
||||||
|
<td>{{remark}}</td>
|
||||||
|
<td>{{dtStart}}</td>
|
||||||
|
<td>{{dtEnd}}</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div>
|
||||||
|
<p>Thank you,</p>
|
||||||
|
<p>LST Team</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
{{!-- <link rel="stylesheet" href="styles/styles.css" /> --}}
|
||||||
|
{{> styles}}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>All,</p>
|
||||||
|
<p>There are currently {{count}} pallets sitting in ppoo that are older than {{checkTime}} {{timeCheck}}.</p>
|
||||||
|
<table >
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Article</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>Lot</th>
|
||||||
|
<th>ProductionDate</th>
|
||||||
|
<th>Running Number</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{#each items}}
|
||||||
|
<tr>
|
||||||
|
<td>{{av}}</td>
|
||||||
|
<td>{{articleDescription}}</td>
|
||||||
|
<td>{{lot}}</td>
|
||||||
|
<td>{{productionDate}}</td>
|
||||||
|
<td>{{runningNumber}}</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div>
|
||||||
|
<p>Thank you,</p>
|
||||||
|
<p>LST Team</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
|
||||||
|
{{!-- <link rel="stylesheet" href="styles/styles.css" /> --}}
|
||||||
|
<style>
|
||||||
|
.email-wrapper {
|
||||||
|
max-width: 80%; /* Limit width to 80% of the window */
|
||||||
|
margin: 0 auto; /* Center the content horizontally */
|
||||||
|
}
|
||||||
|
.email-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
.email-table td {
|
||||||
|
vertical-align: top;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #000;
|
||||||
|
border-radius: 25px; /* Rounded corners */
|
||||||
|
background-color: #f0f0f0; /* Optional: Add a background color */
|
||||||
|
}
|
||||||
|
.email-table h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.remarks {
|
||||||
|
border: 1px solid black;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 25px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="email-wrapper">
|
||||||
|
<p>All,</p>
|
||||||
|
<p>Please see the new blocking order that was created.</p>
|
||||||
|
{{#each items}}
|
||||||
|
<div>
|
||||||
|
<div class="email-table">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<p><strong>Blocking number: </strong>{{blockingNumber}}</p>
|
||||||
|
<p><strong>Blocking Date: </strong>{{blockingDate}}</p>
|
||||||
|
<p><strong>Article: </strong>{{av}}</p>
|
||||||
|
<p><strong>Production Lot: </strong>{{lotNumber}}</p>
|
||||||
|
<p><strong>Line: </strong>{{line}}</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p><strong>Customer: </strong>{{customer}}</p>
|
||||||
|
<p><strong>Blocked pieces /LUs: </strong>{{peicesAndLoadingUnits}}</p>
|
||||||
|
<p><strong>Main defect group: </strong>{{mainDefectGroup}}</p>
|
||||||
|
<p><strong>Main defect: </strong>{{mainDefect}}</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="remarks">
|
||||||
|
<h4>Remarks:</h4>
|
||||||
|
<p>{{remark}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
<br>
|
||||||
|
<p>For further questions please reach out to quality.</p> <br>
|
||||||
|
<p>Thank you,</p> <br>
|
||||||
|
<p>Quality Department</p>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
50
server/services/notifications/utils/views/reprintLabels.hbs
Normal file
50
server/services/notifications/utils/views/reprintLabels.hbs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
{{!-- <link rel="stylesheet" href="styles/styles.css" /> --}}
|
||||||
|
{{> styles}}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>All,</p>
|
||||||
|
<p>The below labels have been reprinted.</p>
|
||||||
|
<table >
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>AV</th>
|
||||||
|
<th>Desciption</th>
|
||||||
|
<th>Label Number</th>
|
||||||
|
<th>Date Added</th>
|
||||||
|
<th>User that created</th>
|
||||||
|
<th>Last time label was printed/updated in the system</th>
|
||||||
|
<th>Who printed/Updated</th>
|
||||||
|
<th>What printer it came from</th>
|
||||||
|
<th>Total reprinted labels</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{{#each items}}
|
||||||
|
<tr>
|
||||||
|
<td>{{av}}</td>
|
||||||
|
<td>{{alias}}</td>
|
||||||
|
<td>{{runningNumber}}</td>
|
||||||
|
<td>{{Add_Date}}</td>
|
||||||
|
<td>{{Add_User}}</td>
|
||||||
|
<td>{{Upd_Date}}</td>
|
||||||
|
<td>{{Upd_User}}</td>
|
||||||
|
<td>{{printer}}</td>
|
||||||
|
<td>{{totalPrinted}}</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p>Thank you,</p>
|
||||||
|
<p>LST Team</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
41
server/services/notifications/utils/views/stagingCheck.hbs
Normal file
41
server/services/notifications/utils/views/stagingCheck.hbs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
{{!-- <link rel="stylesheet" href="styles/styles.css" /> --}}
|
||||||
|
{{> styles}}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>All,</p>
|
||||||
|
<p>There are currently {{count}} pallets sitting in staging that are older than {{checkTime}} {{timeCheck}}.</p>
|
||||||
|
<table >
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Article</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>Lot</th>
|
||||||
|
<th>ProductionDate</th>
|
||||||
|
<th>Running Number</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{#each items}}
|
||||||
|
<tr>
|
||||||
|
<td>{{av}}</td>
|
||||||
|
<td>{{articleDescription}}</td>
|
||||||
|
<td>{{lot}}</td>
|
||||||
|
<td>{{productionDate}}</td>
|
||||||
|
<td>{{runningNumber}}</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div>
|
||||||
|
<p>Thank you,</p>
|
||||||
|
<p>LST Team</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -15,6 +15,6 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"resolveJsonModule": true
|
"resolveJsonModule": true
|
||||||
},
|
},
|
||||||
"include": ["server", "scripts/**/*.ts"],
|
"include": ["server", "scripts/**/*.ts", "testFiles/test-tiPostOrders.ts"],
|
||||||
"exclude": ["node_modules", "frontend", "dist", "testFiles"]
|
"exclude": ["node_modules", "frontend", "dist", "testFiles"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user