feat(materials per day): more work on materials per day
This commit is contained in:
@@ -6,9 +6,26 @@ import {
|
|||||||
parseISO,
|
parseISO,
|
||||||
startOfWeek,
|
startOfWeek,
|
||||||
} from "date-fns";
|
} from "date-fns";
|
||||||
|
import { db } from "../../../../../database/dbclient.js";
|
||||||
|
import { invHistoricalData } from "../../../../../database/schema/historicalINV.js";
|
||||||
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
|
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
|
||||||
import { query } from "../../../sqlServer/prodSqlServer.js";
|
import { query } from "../../../sqlServer/prodSqlServer.js";
|
||||||
import { materialPerDay } from "../../../sqlServer/querys/dataMart/materialPerDay.js";
|
import {
|
||||||
|
materialPerDay,
|
||||||
|
materialPurchasesPerDay,
|
||||||
|
} from "../../../sqlServer/querys/dataMart/materialPerDay.js";
|
||||||
|
import { materialPurchases } from "./materialPurchases.js";
|
||||||
|
import { buildInventoryTimeline } from "./materialWithInv.js";
|
||||||
|
|
||||||
|
const getInv = async () => {
|
||||||
|
const { data, error } = await tryCatch(db.select().from(invHistoricalData));
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
function toDate(val: any) {
|
function toDate(val: any) {
|
||||||
if (val instanceof Date) return val;
|
if (val instanceof Date) return val;
|
||||||
@@ -16,7 +33,7 @@ function toDate(val: any) {
|
|||||||
return new Date(val);
|
return new Date(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sumByMaterialAndWeek(data: any) {
|
function sumByMaterialAndWeek(data: any) {
|
||||||
/** @type {Record<string, Record<string, number>>} */
|
/** @type {Record<string, Record<string, number>>} */
|
||||||
const grouped: any = {};
|
const grouped: any = {};
|
||||||
|
|
||||||
@@ -80,9 +97,45 @@ 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 openingInventory: Record<string, number> = {};
|
||||||
|
const inventoryRows = await getInv();
|
||||||
|
for (const row of inventoryRows) {
|
||||||
|
openingInventory[row.article] = Number(row.total_QTY) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: "material data",
|
message: "material data",
|
||||||
data: sumByMaterialAndWeek(data.data),
|
data: buildInventoryTimeline(
|
||||||
|
sumByMaterialAndWeek(data.data) as any,
|
||||||
|
openingInventory,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { formatISO, parseISO, startOfWeek } from "date-fns";
|
||||||
|
|
||||||
|
function toDate(val: any) {
|
||||||
|
if (val instanceof Date) return val;
|
||||||
|
if (typeof val === "string") return parseISO(val.replace(" ", "T"));
|
||||||
|
return new Date(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const materialPurchases = async (data: any) => {
|
||||||
|
/** @type {Record<string, Record<string, number>>} */
|
||||||
|
const grouped: any = {};
|
||||||
|
|
||||||
|
for (const r of data) {
|
||||||
|
const mat = String(r.MaterialHumanReadableId);
|
||||||
|
const d = toDate(r.CalDate);
|
||||||
|
const week = formatISO(startOfWeek(d, { weekStartsOn: 1 }), {
|
||||||
|
representation: "date",
|
||||||
|
});
|
||||||
|
|
||||||
|
grouped[mat] ??= {};
|
||||||
|
grouped[mat][week] ??= 0;
|
||||||
|
grouped[mat][week] += Number(r.DailyMaterialDemand) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = [];
|
||||||
|
for (const [mat, weeks] of Object.entries(grouped)) {
|
||||||
|
// @ts-ignore
|
||||||
|
for (const [week, total] of Object.entries(weeks)) {
|
||||||
|
result.push({
|
||||||
|
MaterialHumanReadableId: mat,
|
||||||
|
WeekStart: week,
|
||||||
|
WeeklyDemand: Number(total).toFixed(2),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
export const buildInventoryTimeline = (
|
||||||
|
weeklyDemand: Array<{
|
||||||
|
MaterialHumanReadableId: string;
|
||||||
|
WeekStart: string;
|
||||||
|
WeeklyDemand: number;
|
||||||
|
}>,
|
||||||
|
opening: Record<string, number>,
|
||||||
|
) => {
|
||||||
|
// group weekly demand by material
|
||||||
|
const grouped: Record<
|
||||||
|
string,
|
||||||
|
Array<{ WeekStart: string; Demand: number }>
|
||||||
|
> = {};
|
||||||
|
|
||||||
|
for (const d of weeklyDemand) {
|
||||||
|
const mat = d.MaterialHumanReadableId;
|
||||||
|
grouped[mat] ??= [];
|
||||||
|
grouped[mat].push({
|
||||||
|
WeekStart: d.WeekStart,
|
||||||
|
Demand: Number(d.WeeklyDemand),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort weeks chronologically per material
|
||||||
|
for (const mat of Object.keys(grouped)) {
|
||||||
|
grouped[mat].sort(
|
||||||
|
(a, b) =>
|
||||||
|
new Date(a.WeekStart).getTime() - new Date(b.WeekStart).getTime(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: Array<{
|
||||||
|
MaterialHumanReadableId: string;
|
||||||
|
WeekStart: string;
|
||||||
|
OpeningInventory: number;
|
||||||
|
Consumption: number;
|
||||||
|
ClosingInventory: number;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
for (const [mat, weeks] of Object.entries(grouped)) {
|
||||||
|
// get starting inventory from the ERP result
|
||||||
|
let inv = opening[mat] ?? 0;
|
||||||
|
|
||||||
|
for (const w of weeks) {
|
||||||
|
const week = w.WeekStart;
|
||||||
|
const demand = Number(w.Demand);
|
||||||
|
|
||||||
|
const openingInv = inv;
|
||||||
|
const closingInv = openingInv - demand;
|
||||||
|
|
||||||
|
result.push({
|
||||||
|
MaterialHumanReadableId: mat,
|
||||||
|
WeekStart: week,
|
||||||
|
OpeningInventory: Number(openingInv.toFixed(2)),
|
||||||
|
Consumption: Number(demand.toFixed(2)),
|
||||||
|
ClosingInventory: Number(closingInv.toFixed(2)),
|
||||||
|
});
|
||||||
|
|
||||||
|
inv = closingInv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
|
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
|
||||||
import { apiHit } from "../../../globalUtils/apiHits.js";
|
import { apiHit } from "../../../globalUtils/apiHits.js";
|
||||||
import { responses } from "../../../globalUtils/routeDefs/responses.js";
|
import { responses } from "../../../globalUtils/routeDefs/responses.js";
|
||||||
|
import materialPerDayCheck from "../controller/materialsPerDay/materialPerDay.js";
|
||||||
import fifoIndexCheck from "../controller/notifications/fifoIndex.js";
|
import fifoIndexCheck from "../controller/notifications/fifoIndex.js";
|
||||||
import materialPerDayCheck from "../controller/notifications/materialPerDay.js";
|
|
||||||
|
|
||||||
const app = new OpenAPIHono({ strict: false });
|
const app = new OpenAPIHono({ strict: false });
|
||||||
|
|
||||||
|
|||||||
@@ -113,3 +113,21 @@ ORDER BY
|
|||||||
n.ProductionLotHumanReadableId
|
n.ProductionLotHumanReadableId
|
||||||
OPTION (MAXRECURSION 0);
|
OPTION (MAXRECURSION 0);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const materialPurchasesPerDay = `
|
||||||
|
use AlplaPROD_test1
|
||||||
|
declare @startDate nvarchar(max) = '[startDate]'
|
||||||
|
declare @endDate nvarchar(max) = '[endDate]'
|
||||||
|
SELECT
|
||||||
|
[IdBestellung] as purhcaseOrder
|
||||||
|
|
||||||
|
,[IdArtikelVarianten]
|
||||||
|
,convert(date, [BestellDatum], 120) as orderDate
|
||||||
|
,convert(date, [Lieferdatum], 120) as deliveryDate
|
||||||
|
,[BestellMenge] as qty
|
||||||
|
,[BestellMengeVPK] as pallets
|
||||||
|
|
||||||
|
FROM [dbo].[T_Bestellpositionen]
|
||||||
|
|
||||||
|
where PositionsStatus = 2 and convert(date, [Lieferdatum], 120) between @startDate and @endDate --and IdBestellung in( 5535,5534,5513)
|
||||||
|
`;
|
||||||
|
|||||||
Reference in New Issue
Block a user