feat(puchase history): purhcase history changed to long running no notification

This commit is contained in:
2026-04-08 15:55:25 -05:00
parent 28c226ddbc
commit 34b0abac36
15 changed files with 5918 additions and 20 deletions

View File

@@ -15,14 +15,14 @@ export const alplaPurchaseHistory = pgTable("alpla_purchase_history", {
revision: integer("revision"), revision: integer("revision"),
confirmed: integer("confirmed"), confirmed: integer("confirmed"),
status: integer("status"), status: integer("status"),
statusText: integer("status_text"), statusText: text("status_text"),
journalNum: integer("journal_num"), journalNum: integer("journal_num"),
add_date: timestamp("add_date").defaultNow(), add_date: timestamp("add_date").defaultNow(),
upd_date: timestamp("upd_date").defaultNow(),
add_user: text("add_user"), add_user: text("add_user"),
upd_user: text("upd_user"), upd_user: text("upd_user"),
upd_date: timestamp("upd_date").defaultNow(),
remark: text("remark"), remark: text("remark"),
approvedStatus: text("approved_status"), approvedStatus: text("approved_status").default("pending"),
position: jsonb("position").default([]), position: jsonb("position").default([]),
createdAt: timestamp("created_at").defaultNow(), createdAt: timestamp("created_at").defaultNow(),
}); });

View File

@@ -15,16 +15,18 @@ IdBestellung as apo
when 1 then 'Created' when 1 then 'Created'
when 2 then 'Ordered' when 2 then 'Ordered'
when 22 then 'Reopened' when 22 then 'Reopened'
when 11 then 'Reopened'
when 4 then 'Planned' when 4 then 'Planned'
when 5 then 'Partly Delivered' when 5 then 'Partly Delivered'
when 6 then 'Delivered' when 6 then 'Delivered'
when 7 then 'Canceled' when 7 then 'Canceled'
when 8 then 'Closed' when 8 then 'Closed'
else 'Unknown' end as statusText else 'Unknown' end as statusText
,po.Add_User ,po.IdJournal as journalNum -- use this to validate if we used it already.
,po.Add_Date ,po.Add_User as add_user
,po.Upd_User ,po.Add_Date as add_date
,po.Upd_Date ,po.Upd_User as upd_user
,po.Upd_Date as upd_Date
,po.Bemerkung as remark ,po.Bemerkung as remark
,po.IdJournal as journal -- use this to validate if we used it already. ,po.IdJournal as journal -- use this to validate if we used it already.
,isnull(( ,isnull((
@@ -55,7 +57,7 @@ left join
a.IdArtikelvarianten = o.IdArtikelVarianten a.IdArtikelvarianten = o.IdArtikelVarianten
where o.IdBestellung = po.IdBestellung where o.IdBestellung = po.IdBestellung
for json path for json path
), '[]') as postion ), '[]') as position
--,* --,*
from T_Bestellungen (nolock) as po from T_Bestellungen (nolock) as po
where po.Upd_Date > dateadd(MINUTE, -@intervalCheck, getdate()) where po.Upd_Date > dateadd(MINUTE, -@intervalCheck, getdate())

View File

@@ -4,6 +4,10 @@
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { db } from "../db/db.controller.js"; import { db } from "../db/db.controller.js";
import {
alplaPurchaseHistory,
type NewAlplaPurchaseHistory,
} from "../db/schema/alplapurchase.schema.js";
import { settings } from "../db/schema/settings.schema.js"; import { settings } from "../db/schema/settings.schema.js";
import { createLogger } from "../logger/logger.controller.js"; import { createLogger } from "../logger/logger.controller.js";
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js"; import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
@@ -14,6 +18,7 @@ import {
import { createCronJob } from "../utils/croner.utils.js"; import { createCronJob } from "../utils/croner.utils.js";
import { delay } from "../utils/delay.utils.js"; import { delay } from "../utils/delay.utils.js";
import { returnFunc } from "../utils/returnHelper.utils.js"; import { returnFunc } from "../utils/returnHelper.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js";
const log = createLogger({ module: "purchase", subModule: "purchaseMonitor" }); const log = createLogger({ module: "purchase", subModule: "purchaseMonitor" });
@@ -38,17 +43,44 @@ export const monitorAlplaPurchase = async () => {
} }
if (purchaseMonitor[0]?.active) { if (purchaseMonitor[0]?.active) {
createCronJob("opendock_sync", "* */5 * * * *", async () => { createCronJob("purchaseMonitor", "0 */5 * * * *", async () => {
try { try {
const result = await prodQuery( const result = await prodQuery(
sqlQuery.query.replace( sqlQuery.query.replace(
"[interval]", "[interval]",
`'${purchaseMonitor[0]?.value || "5"}'`, `${purchaseMonitor[0]?.value || "5"}`,
), ),
"Get release info", "Get release info",
); );
log.debug(
{},
`There are ${result.data.length} pending to be updated from the last ${purchaseMonitor[0]?.value}`,
);
if (result.data.length) { if (result.data.length) {
const convertedData = result.data.map((i) => ({
...i,
position: JSON.parse(i.position),
})) as NewAlplaPurchaseHistory;
const { data, error } = await tryCatch(
db.insert(alplaPurchaseHistory).values(convertedData).returning(),
);
if (data) {
log.debug(
{ data },
"New data was just added to alpla purchase history",
);
}
if (error) {
log.error(
{ error },
"There was an error adding alpla purchase history",
);
}
await delay(500); await delay(500);
} }
} catch (e) { } catch (e) {
@@ -57,6 +89,8 @@ export const monitorAlplaPurchase = async () => {
"Error occurred while running the monitor job", "Error occurred while running the monitor job",
); );
log.error({ error: e }, "Error occurred while running the monitor job"); log.error({ error: e }, "Error occurred while running the monitor job");
return;
} }
}); });
} }

View File

@@ -8,7 +8,7 @@ const newSettings: NewSetting[] = [
// feature settings // feature settings
{ {
name: "opendock_sync", name: "opendock_sync",
value: "0", value: "15",
active: false, active: false,
description: "Dock Scheduling system", description: "Dock Scheduling system",
moduleName: "opendock", moduleName: "opendock",
@@ -69,7 +69,7 @@ const newSettings: NewSetting[] = [
{ {
name: "purchaseMonitor", name: "purchaseMonitor",
value: "5", value: "5",
active: false, active: true,
description: "Monitors alpla purchase fo all changes", description: "Monitors alpla purchase fo all changes",
moduleName: "purchase", moduleName: "purchase",
settingType: "feature", settingType: "feature",

View File

@@ -10,6 +10,7 @@ import {
killOpendockSocket, killOpendockSocket,
opendockSocketMonitor, opendockSocketMonitor,
} from "../opendock/opendockSocketMonitor.utils.js"; } from "../opendock/opendockSocketMonitor.utils.js";
import { monitorAlplaPurchase } from "../purchase/purchase.controller.js";
import { import {
createCronJob, createCronJob,
resumeCronJob, resumeCronJob,
@@ -31,8 +32,24 @@ export const featureControl = async (data: Setting) => {
createCronJob("opendockAptCleanup", "0 30 5 * * *", () => createCronJob("opendockAptCleanup", "0 30 5 * * *", () =>
dbCleanup("opendockApt", 90), dbCleanup("opendockApt", 90),
); );
} else { }
if (data.name === "opendock_sync" && !data.active) {
killOpendockSocket(); killOpendockSocket();
stopCronJob("opendockAptCleanup"); stopCronJob("opendockAptCleanup");
} }
// purchase stuff
if (data.name === "purchaseMonitor" && data.active) {
monitorAlplaPurchase();
}
if (data.name === "purchaseMonitor" && !data.active) {
stopCronJob("purchaseMonitor");
}
// this means the data time has changed
if (data.name === "purchaseMonitor" && data.value) {
monitorAlplaPurchase();
}
}; };

View File

@@ -18,7 +18,9 @@ export interface JobInfo {
// Store running cronjobs // Store running cronjobs
export const runningCrons: Record<string, Cron> = {}; export const runningCrons: Record<string, Cron> = {};
const activeRuns = new Set<string>();
const log = createLogger({ module: "system", subModule: "croner" }); const log = createLogger({ module: "system", subModule: "croner" });
const cronStats: Record<string, { created: number; replaced: number }> = {};
// how to se the times // how to se the times
// * ┌──────────────── (optional) second (0 - 59) // * ┌──────────────── (optional) second (0 - 59)
@@ -38,17 +40,36 @@ const log = createLogger({ module: "system", subModule: "croner" });
* @param name Name of the job we want to run * @param name Name of the job we want to run
* @param schedule Cron expression (example: `*\/5 * * * * *`) * @param schedule Cron expression (example: `*\/5 * * * * *`)
* @param task Async function that will run * @param task Async function that will run
* @param source we can add where it came from to assist in getting this tracked down, more for debugging
*/ */
export const createCronJob = async ( export const createCronJob = async (
name: string, name: string,
schedule: string, // cron string with 8 8 IE: */5 * * * * * every 5th second schedule: string, // cron string with 8 8 IE: */5 * * * * * every 5th second
task: () => Promise<void>, // what function are we passing over task: () => Promise<void>, // what function are we passing over
source = "unknown",
) => { ) => {
// get the timezone based on the os timezone set // get the timezone based on the os timezone set
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
// initial go so just store it this is more for debugging to see if something crazy keeps happening
if (!cronStats[name]) {
cronStats[name] = { created: 0, replaced: 0 };
}
// Destroy existing job if it exist // Destroy existing job if it exist
if (runningCrons[name]) { if (runningCrons[name]) {
cronStats[name].replaced += 1;
log.warn(
{
job: name,
source,
oldSchedule: runningCrons[name].getPattern?.(),
newSchedule: schedule,
replaceCount: cronStats[name].replaced,
},
`Cron job "${name}" already existed and is being replaced`,
);
runningCrons[name].stop(); runningCrons[name].stop();
} }
@@ -61,6 +82,13 @@ export const createCronJob = async (
name: name, name: name,
}, },
async () => { async () => {
if (activeRuns.has(name)) {
log.warn({ jobName: name }, "Skipping overlapping cron execution");
return;
}
activeRuns.add(name);
const startedAt = new Date(); const startedAt = new Date();
const start = Date.now(); const start = Date.now();
@@ -91,14 +119,19 @@ export const createCronJob = async (
.where(eq(jobAuditLog.id, executionId)); .where(eq(jobAuditLog.id, executionId));
} catch (e: any) { } catch (e: any) {
if (executionId) { if (executionId) {
await db.update(jobAuditLog).set({ await db
finishedAt: new Date(), .update(jobAuditLog)
durationMs: Date.now() - start, .set({
status: "error", finishedAt: new Date(),
errorMessage: e.message, durationMs: Date.now() - start,
errorStack: e.stack, status: "error",
}); errorMessage: e.message,
errorStack: e.stack,
})
.where(eq(jobAuditLog.id, executionId));
} }
} finally {
activeRuns.delete(name);
} }
}, },
); );

View File

@@ -0,0 +1,2 @@
ALTER TABLE "alpla_purchase_history" ADD COLUMN "journal_num" integer;--> statement-breakpoint
ALTER TABLE "alpla_purchase_history" ADD COLUMN "approved_status" text;

View File

@@ -0,0 +1 @@
ALTER TABLE "alpla_purchase_history" ALTER COLUMN "approved_status" SET DEFAULT 'pending';

View File

@@ -0,0 +1 @@
ALTER TABLE "alpla_purchase_history" ALTER COLUMN "status_text" SET DATA TYPE text;

View File

@@ -0,0 +1,6 @@
ALTER TABLE "opendock_apt" ALTER COLUMN "release" SET NOT NULL;--> statement-breakpoint
ALTER TABLE "opendock_apt" ALTER COLUMN "appointment" SET NOT NULL;--> statement-breakpoint
ALTER TABLE "opendock_apt" ALTER COLUMN "upd_date" SET NOT NULL;--> statement-breakpoint
ALTER TABLE "opendock_apt" ALTER COLUMN "created_at" SET NOT NULL;--> statement-breakpoint
CREATE INDEX "opendock_apt_release_idx" ON "opendock_apt" USING btree ("release");--> statement-breakpoint
CREATE INDEX "opendock_apt_opendock_id_idx" ON "opendock_apt" USING btree ("open_dock_apt_id");

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -148,6 +148,34 @@
"when": 1775566910220, "when": 1775566910220,
"tag": "0020_stale_ma_gnuci", "tag": "0020_stale_ma_gnuci",
"breakpoints": true "breakpoints": true
},
{
"idx": 21,
"version": "7",
"when": 1775647109925,
"tag": "0021_slimy_master_mold",
"breakpoints": true
},
{
"idx": 22,
"version": "7",
"when": 1775649219780,
"tag": "0022_large_sumo",
"breakpoints": true
},
{
"idx": 23,
"version": "7",
"when": 1775650901523,
"tag": "0023_normal_hellion",
"breakpoints": true
},
{
"idx": 24,
"version": "7",
"when": 1775661516749,
"tag": "0024_absent_barracuda",
"breakpoints": true
} }
] ]
} }