test(materials per day): work on getting this running better

This commit is contained in:
2025-11-19 18:42:58 -06:00
parent 9aa0b31278
commit a30eebf5d3
9 changed files with 373 additions and 186 deletions

View File

@@ -9,11 +9,14 @@ import {
import { db } from "../../../../../database/dbclient.js";
import { invHistoricalData } from "../../../../../database/schema/historicalINV.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { createLog } from "../../../logger/logger.js";
import { query } from "../../../sqlServer/prodSqlServer.js";
import {
materialPerDay,
materialPurchasesPerDay,
} from "../../../sqlServer/querys/dataMart/materialPerDay.js";
import { singleArticle } from "../../../sqlServer/querys/misc/singleArticle.js";
import { sendEmail } from "../sendMail.js";
import { materialPurchases } from "./materialPurchases.js";
import { buildInventoryTimeline } from "./materialWithInv.js";
@@ -99,43 +102,77 @@ export default async function materialPerDayCheck() {
// purchases
const { data: p, error: pe } = (await tryCatch(
query(
materialPurchasesPerDay
.replace("[startDate]", startDate)
.replace("[endDate]", endDate),
"material check",
),
)) as any;
if (error) {
return {
success: false,
message: "Error getting the material data",
error,
};
}
if (!data.success) {
return {
success: false,
message: data.message,
data: [],
};
}
const pOrders = (await materialPurchases({ startDate, endDate })) as any;
//console.log(pOrders);
const openingInventory: Record<string, number> = {};
const inventoryRows = await getInv();
for (const row of inventoryRows) {
openingInventory[row.article] = Number(row.total_QTY) || 0;
}
const materialsDemand = buildInventoryTimeline(
sumByMaterialAndWeek(data.data) as any,
openingInventory,
pOrders,
);
const { data: av, error: eav } = await tryCatch(
query(singleArticle.replace("[av]", "107"), "single article"),
);
const formattedMaterials = materialsDemand
.filter((n) => n.MaterialHumanReadableId === `${av?.data[0].article}`)
.map((i) => ({
...i,
OpeningInventory: i.OpeningInventory?.toLocaleString("en-US", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}),
Purchases: i.Purchases?.toLocaleString("en-US", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}),
Consumption: i.Consumption?.toLocaleString("en-US", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}),
ClosingInventory: i.ClosingInventory?.toLocaleString("en-US", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}),
}));
// send the email stuff
const emailSetup = {
email: "blake.matthes@alpla.com",
subject: `Material Week.`,
template: "materialPerDay",
context: {
items: formattedMaterials,
article: av?.data[0].combined,
},
};
const { data: sentEmail, error: sendEmailError } = await tryCatch(
sendEmail(emailSetup),
);
if (sendEmailError) {
createLog(
"error",
"blocking",
"notify",
"Failed to send email, will try again on next interval",
);
return {
success: false,
message: "Failed to send email, will try again on next interval",
};
}
return {
success: true,
message: "material data",
data: buildInventoryTimeline(
sumByMaterialAndWeek(data.data) as any,
openingInventory,
),
data: formattedMaterials,
};
}

View File

@@ -1,4 +1,7 @@
import { formatISO, parseISO, startOfWeek } from "date-fns";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { query } from "../../../sqlServer/prodSqlServer.js";
import { materialPurchasesPerDay } from "../../../sqlServer/querys/dataMart/materialPerDay.js";
function toDate(val: any) {
if (val instanceof Date) return val;
@@ -6,20 +9,52 @@ function toDate(val: any) {
return new Date(val);
}
export const materialPurchases = async (data: any) => {
export const materialPurchases = async ({
startDate,
endDate,
}: {
startDate: string;
endDate: string;
}) => {
/** @type {Record<string, Record<string, number>>} */
const grouped: any = {};
for (const r of data) {
const { data: p, error } = (await tryCatch(
query(
materialPurchasesPerDay
.replace("[startDate]", startDate)
.replace("[endDate]", endDate),
"material check",
),
)) as any;
if (error) {
return {
success: false,
message: "Error getting the material data",
error,
};
}
if (!p.success) {
return {
success: false,
message: p.message,
data: [],
};
}
for (const r of p.data) {
const pOrder = String(r.purhcaseOrder);
const mat = String(r.MaterialHumanReadableId);
const d = toDate(r.CalDate);
const d = toDate(r.deliveryDate);
const week = formatISO(startOfWeek(d, { weekStartsOn: 1 }), {
representation: "date",
});
grouped[mat] ??= {};
grouped[mat][week] ??= 0;
grouped[mat][week] += Number(r.DailyMaterialDemand) || 0;
grouped[mat][week] += Number(r.qty) || 0;
}
const result = [];
@@ -29,7 +64,7 @@ export const materialPurchases = async (data: any) => {
result.push({
MaterialHumanReadableId: mat,
WeekStart: week,
WeeklyDemand: Number(total).toFixed(2),
WeeklyPurchase: Number(total).toFixed(2),
});
}
}

View File

@@ -5,25 +5,51 @@ export const buildInventoryTimeline = (
WeeklyDemand: number;
}>,
opening: Record<string, number>,
weeklyPurchases?: Array<{
MaterialHumanReadableId: string;
WeekStart: string;
WeeklyPurchase: number;
}>,
) => {
// group weekly demand by material
const grouped: Record<
const groupedDemand: Record<
string,
Array<{ WeekStart: string; Demand: number }>
> = {};
for (const d of weeklyDemand) {
const mat = d.MaterialHumanReadableId;
grouped[mat] ??= [];
grouped[mat].push({
groupedDemand[mat] ??= [];
groupedDemand[mat].push({
WeekStart: d.WeekStart,
Demand: Number(d.WeeklyDemand),
});
}
// sort weeks chronologically per material
for (const mat of Object.keys(grouped)) {
grouped[mat].sort(
// group weekly purchases by material
const groupedPurchases: Record<
string,
Array<{ WeekStart: string; Purchase: number }>
> = {};
if (weeklyPurchases) {
for (const p of weeklyPurchases) {
const mat = p.MaterialHumanReadableId;
groupedPurchases[mat] ??= [];
groupedPurchases[mat].push({
WeekStart: p.WeekStart,
Purchase: Number(p.WeeklyPurchase),
});
}
}
// sort both chronologically
for (const mat of Object.keys(groupedDemand)) {
groupedDemand[mat].sort(
(a, b) =>
new Date(a.WeekStart).getTime() - new Date(b.WeekStart).getTime(),
);
}
for (const mat of Object.keys(groupedPurchases)) {
groupedPurchases[mat].sort(
(a, b) =>
new Date(a.WeekStart).getTime() - new Date(b.WeekStart).getTime(),
);
@@ -33,25 +59,30 @@ export const buildInventoryTimeline = (
MaterialHumanReadableId: string;
WeekStart: string;
OpeningInventory: number;
Purchases: number;
Consumption: number;
ClosingInventory: number;
}> = [];
for (const [mat, weeks] of Object.entries(grouped)) {
// get starting inventory from the ERP result
for (const [mat, weeks] of Object.entries(groupedDemand)) {
let inv = opening[mat] ?? 0;
const purchasesForMaterial = groupedPurchases[mat] ?? [];
for (const w of weeks) {
const week = w.WeekStart;
const demand = Number(w.Demand);
for (const week of weeks) {
const demand = Number(week.Demand);
const purchase =
purchasesForMaterial.find((p) => p.WeekStart === week.WeekStart)
?.Purchase ?? 0;
const openingInv = inv;
const closingInv = openingInv - demand;
const adjustedInv = openingInv + purchase;
const closingInv = adjustedInv - demand;
result.push({
MaterialHumanReadableId: mat,
WeekStart: week,
WeekStart: week.WeekStart,
OpeningInventory: Number(openingInv.toFixed(2)),
Purchases: Number(purchase.toFixed(2)),
Consumption: Number(demand.toFixed(2)),
ClosingInventory: Number(closingInv.toFixed(2)),
});