Compare commits

..

48 Commits

Author SHA1 Message Date
468f933168 fix(eom stats): corrections to the eom inv stuff to be proper tiems 2025-08-28 10:15:41 -05:00
8c296c5b78 test(forklifts): forklift starting process 2025-08-28 10:15:13 -05:00
53ed2c4e6a feat(materials): added in a bigger window on eom transfer lots 2025-08-28 10:14:41 -05:00
0a6ddea8c0 refactor(materials): changes to allow exact and eom transfers 2025-08-27 17:17:32 -05:00
df423192bf refactor(labeling): moved bookin fails inside bookin as it could be off 2025-08-27 17:17:03 -05:00
2f908398bc ci(release): bump build number to 575 2025-08-27 16:08:58 -05:00
bf8203bbee ci(release): bump build number to 574 2025-08-27 15:50:49 -05:00
e92eccf785 ci(release): bump build number to 573 2025-08-27 14:43:04 -05:00
f4f3de49ca fix(material check): alt mm causing issues and utilizing an 80% to just be ok 2025-08-25 18:34:09 -05:00
788d6367a3 refactor(labeling): removed the wrong import 2025-08-25 15:47:18 -05:00
24a97afe06 fix(fake edi): removed console log 2025-08-25 14:40:21 -05:00
37f82a9710 refactor(tms intergration): corrected how we added gl coding 2025-08-25 14:39:54 -05:00
369d16018c fix(bookin): corrected the error received from the book in fail 2025-08-25 14:38:00 -05:00
68901a857a fix(dm): corrected the remark section so its properly sent over 2025-08-25 14:09:13 -05:00
171763184c ci(release): bump build number to 572 2025-08-25 13:58:47 -05:00
b7de2a8dbe ci(release): bump build number to 571 2025-08-25 13:15:06 -05:00
e78083f496 ci(release): bump build number to 570 2025-08-25 12:18:17 -05:00
3f3b64bf2b ci(release): bump build number to 569 2025-08-25 12:15:27 -05:00
123bb127e8 ci(release): bump build number to 568 2025-08-21 07:33:43 -05:00
8d6ead3aa1 fix(produser): changes to include DM 2025-08-21 05:55:41 -05:00
3148aa79d7 refactor(materials): changes for permissions on material consume 2025-08-21 05:55:20 -05:00
4486fe2436 fix(ocp): zechetti type correction to include the printer name 2025-08-21 05:54:48 -05:00
0ba338d480 feat(rfid): new check to remove tags that have been at a line longer than 6 hours 2025-08-21 05:18:49 -05:00
846ac479b1 fix(transferlots): missed adding this 2025-08-21 05:14:03 -05:00
73d38ba3fe refactor(notifcations): changed hour to min in ti intergrations 2025-08-21 05:13:34 -05:00
27586e923a feat(ocp): zechetti 1 added in 2025-08-21 05:13:06 -05:00
662a951b98 feat(tms): a clean up function was added to remove releases added as blockers older than 45d 2025-08-21 05:10:25 -05:00
0c54cecbd4 ci(release): bump build number to 567 2025-08-20 20:51:22 -05:00
bcf5378966 ci(release): bump build number to 566 2025-08-20 20:04:51 -05:00
c5668e6cf1 chore(release): 2.25.0 2025-08-20 20:04:05 -05:00
213814b868 ci(release): bump build number to 565 2025-08-20 19:02:42 -05:00
7d5b0c46c1 ci(release): bump build number to 564 2025-08-19 17:37:40 -05:00
595e22e8e9 ci(release): bump build number to 563 2025-08-19 17:04:13 -05:00
a6f18554b8 ci(release): bump build number to 562 2025-08-19 17:00:13 -05:00
88f61c8eaa feat(ocp): materials contorls and transfer to next lot logic 2025-08-19 16:00:49 -05:00
a183279268 ci(release): bump build number to 561 2025-08-17 18:10:25 -05:00
5156c8bf7b ci(release): bump build number to 560 2025-08-14 09:32:25 -05:00
4669ff95dc ci(release): bump build number to 559 2025-08-13 22:52:44 -05:00
ed93992165 ci(release): bump build number to 558 2025-08-13 16:32:23 -05:00
f16e2bf53b ci(release): bump build number to 557 2025-08-13 15:50:35 -05:00
a84998438c ci(release): bump build number to 556 2025-08-13 11:33:45 -05:00
83469105f0 ci(release): bump build number to 555 2025-08-12 17:53:08 -05:00
835ae58f04 ci(release): bump build number to 554 2025-08-12 16:26:39 -05:00
1498a19121 ci(release): bump build number to 553 2025-08-12 14:28:42 -05:00
ca96849991 ci(release): bump build number to 552 2025-08-12 13:55:49 -05:00
27b37f9849 ci(release): bump build number to 551 2025-08-12 11:24:50 -05:00
7f81a2e09a ci(release): bump build number to 550 2025-08-11 12:24:30 -05:00
e14abd3644 ci(release): bump build number to 549 2025-08-11 08:22:44 -05:00
37 changed files with 2040 additions and 293 deletions

View File

@@ -1,5 +1,36 @@
# All CHanges to LST can be found below. # All CHanges to LST can be found below.
## [2.25.0](https://git.tuffraid.net/cowch/lstV2/compare/v2.24.1...v2.25.0) (2025-08-21)
### 🐛 Bug fixes
* **dm page:** correction to the insturcitons ([a36552f](https://git.tuffraid.net/cowch/lstV2/commits/a36552fd9b9c77f8ecee8b36f45e613383841f95))
* **dyco:** correction to disable the camera if ocme is off ([c7bb128](https://git.tuffraid.net/cowch/lstV2/commits/c7bb12822b13c0c1c929d2c8a9ab150cd0feeff2))
* **gotransport:** error handling so we dont get spammed with errors ([2eb6fa7](https://git.tuffraid.net/cowch/lstV2/commits/2eb6fa77946d5f8572ffc5baa47da602482bce2b))
* **https fixes:** made it so the settings can be grabbed via https ([803c963](https://git.tuffraid.net/cowch/lstV2/commits/803c963f964f26095b2aa6a7d0a60e03615d4c17))
* **inv cards:** correction to properly display the names ([2288884](https://git.tuffraid.net/cowch/lstV2/commits/22888848291aa3df4ebbdd224656731fbb305fa7))
* **inv query:** error in improper placed , in the query ([397f1da](https://git.tuffraid.net/cowch/lstV2/commits/397f1da595fd8a1e1c2a630a3650eb8715604c82))
* **mainmaterial check:** if the machine dose not require mm to be staged properly ignore ([0d17fef](https://git.tuffraid.net/cowch/lstV2/commits/0d17fef1a1c75dd0f27988fd2e3527b508b915cb))
### 🛠️ Code Refactor
* **migration progress:** moved to start looking at the go backedn ([a0179a4](https://git.tuffraid.net/cowch/lstV2/commits/a0179a41bac93d2a7320802e9d70aa966ed79ae4))
* **migrations:** not needed but we have it and needed to correct the settings ([2ff7b9b](https://git.tuffraid.net/cowch/lstV2/commits/2ff7b9baf9ca288f8a33bec3ab1a2ba331ace6b9))
* **notifications:** refactored the cron job system so we can utilize outside the service ([103171c](https://git.tuffraid.net/cowch/lstV2/commits/103171c924a9de78b0a7600abb455fdd6f4bfea1))
* **siloadjustment:** refactored to get the settings from the state vs direct from db ([8145dc8](https://git.tuffraid.net/cowch/lstV2/commits/8145dc800dced31860a926c80eca72cb39433b29))
### 🌟 Enhancements
* **dm:** changes to have a default time if nothing is passed in the excel ([9e5577e](https://git.tuffraid.net/cowch/lstV2/commits/9e5577e6bb4ff3b6c4004288e177fbab322a4b44))
* **eom:** added in hostorical data and deletion for data over 45 days ([52345bc](https://git.tuffraid.net/cowch/lstV2/commits/52345bc94c9e8abc82150fb371a9ba0d0757f16a))
* **migration start:** this starts the migration of all settings to look at the go backend vs this ([998e84f](https://git.tuffraid.net/cowch/lstV2/commits/998e84f5648148c9a94df7177a3d311e16bf4614))
* **ocp:** materials contorls and transfer to next lot logic ([88f61c8](https://git.tuffraid.net/cowch/lstV2/commits/88f61c8eaa32a581094b04b5e18c654040dbeb92))
* **prodrole:** added in planner role ([6ccf500](https://git.tuffraid.net/cowch/lstV2/commits/6ccf500e5eb82125f7bcd3d764282a4b8a750be9))
* **psi:** psi querys added and av grab right now ([8d63f7f](https://git.tuffraid.net/cowch/lstV2/commits/8d63f7f6b0981d604b628ff2f710b8eb41d32837))
### [2.24.1](https://git.tuffraid.net/cowch/lstV2/compare/v2.24.0...v2.24.1) (2025-07-25) ### [2.24.1](https://git.tuffraid.net/cowch/lstV2/compare/v2.24.0...v2.24.1) (2025-07-25)

View File

View File

View File

View File

@@ -21,6 +21,7 @@
"@radix-ui/react-select": "^2.2.5", "@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.12", "@radix-ui/react-tabs": "^1.1.12",
"@radix-ui/react-tooltip": "^1.2.7", "@radix-ui/react-tooltip": "^1.2.7",
"@react-pdf/renderer": "^4.3.0", "@react-pdf/renderer": "^4.3.0",
@@ -2072,6 +2073,41 @@
} }
} }
}, },
"node_modules/@radix-ui/react-switch": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz",
"integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.3",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-use-controllable-state": "1.2.2",
"@radix-ui/react-use-previous": "1.1.1",
"@radix-ui/react-use-size": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-switch/node_modules/@radix-ui/primitive": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
"integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
"license": "MIT"
},
"node_modules/@radix-ui/react-tabs": { "node_modules/@radix-ui/react-tabs": {
"version": "1.1.12", "version": "1.1.12",
"resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.12.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.12.tgz",
@@ -5316,18 +5352,6 @@
"node": ">= 4" "node": ">= 4"
} }
}, },
"node_modules/immer": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
"license": "MIT",
"optional": true,
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
}
},
"node_modules/import-fresh": { "node_modules/import-fresh": {
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",

View File

@@ -25,6 +25,7 @@
"@radix-ui/react-select": "^2.2.5", "@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.12", "@radix-ui/react-tabs": "^1.1.12",
"@radix-ui/react-tooltip": "^1.2.7", "@radix-ui/react-tooltip": "^1.2.7",
"@react-pdf/renderer": "^4.3.0", "@react-pdf/renderer": "^4.3.0",

View File

@@ -1,131 +1,9 @@
import {useSessionStore} from "@/lib/store/sessionStore"; import MaterialCheck from "./materialCheck/MaterialCheck";
import {LstCard} from "../extendedUI/LstCard";
import {Tabs, TabsContent, TabsList, TabsTrigger} from "../ui/tabs";
import {useModuleStore} from "@/lib/store/useModuleStore";
import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from "../ui/table";
import {Skeleton} from "../ui/skeleton";
import {Link, useRouter} from "@tanstack/react-router";
import {Popover, PopoverContent, PopoverTrigger} from "../ui/popover";
import {Button} from "../ui/button";
import {cn} from "@/lib/utils";
import {CalendarIcon} from "lucide-react";
import {format, startOfMonth} from "date-fns";
import {Calendar} from "../ui/calendar";
import {useState} from "react";
import {toast} from "sonner";
import KFP from "./KFP";
export default function EomPage() { export default function EomPage() {
const {modules} = useModuleStore();
const {user} = useSessionStore();
const router = useRouter();
const [date, setDate] = useState<Date>();
if (!user) {
router.navigate({to: "/"});
}
const eomMod = modules.filter((m) => m.name === "eom");
// the users current role for eom is?
const role: any = user?.roles.filter((r) => r.module_id === eomMod[0].module_id) || "";
const tabs = [
{key: "kfp", label: "Key Figures", roles: ["admin", "systemAdmin"], content: <KFP />},
{key: "fg", label: "Finished Goods", roles: ["admin", "systemAdmin"], content: <DummyContent />},
{key: "mm", label: "Main Material", roles: ["admin", "systemAdmin"], content: <DummyContent />},
{key: "mb", label: "Master Batch", roles: ["admin", "systemAdmin"], content: <DummyContent />},
{key: "ab", label: "Additive", roles: ["admin", "systemAdmin"], content: <DummyContent />},
{key: "pp", label: "Purchased Preforms", roles: ["admin", "systemAdmin"], content: <DummyContent />},
{key: "pre", label: "Preforms", roles: ["admin", "systemAdmin"], content: <DummyContent />},
{key: "pkg", label: "Packaging", roles: ["admin", "systemAdmin"], content: <DummyContent />},
{key: "ui", label: "Undefined Items", roles: ["admin"], content: <DummyContent />},
];
return ( return (
<div className="m-2 w-screen"> <div className="m-2 w-screen">
<div className="mb-2 flex flex-row"> <MaterialCheck />
<Popover>
<PopoverTrigger asChild>
<Button
variant={"outline"}
className={cn(
"w-[280px] justify-start text-left font-normal",
!date && "text-muted-foreground"
)}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{date ? format(date, "PPP") : <span>Pick a date</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar mode="single" selected={date} onSelect={setDate} initialFocus />
</PopoverContent>
</Popover>
<div className="ml-2">
<Button onClick={() => toast.success(`Getting data for ${startOfMonth(date!)}-${date}`)}>
<span className="text-sm">Update Data</span>
</Button>
</div>
</div>
<Tabs defaultValue="mm">
<TabsList>
{tabs.map((tab) => {
if (tab.roles.includes(role[0].role))
return <TabsTrigger value={tab.key}>{tab.label}</TabsTrigger>;
})}
</TabsList>
{tabs.map((tab) => {
if (tab.roles.includes(role[0].role))
return <TabsContent value={tab.key}>{tab.content}</TabsContent>;
})}
</Tabs>
</div> </div>
); );
} }
function DummyContent() {
return (
<LstCard className="w-5/6">
<Table>
<TableHeader>
<TableRow>
<TableHead>Av</TableHead>
<TableHead>Description</TableHead>
<TableHead>Material Type</TableHead>
<TableHead>Waste</TableHead>
<TableHead>Loss / Gain $$</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{Array(10)
.fill(0)
.map((_, i) => (
<TableRow key={i}>
<TableCell className="font-medium m-2">
<Link to="/article/$av" params={{av: `${i}`}}>
{i}
</Link>
</TableCell>
<TableCell className="font-medium">
<Skeleton className="h-4" />
</TableCell>
<TableCell>
<Skeleton className="h-4" />
</TableCell>
<TableCell>
<Skeleton className="h-4" />
</TableCell>
<TableCell>
<Skeleton className="h-4" />
</TableCell>
{/* <TableCell>
<Skeleton className="h-4" />
</TableCell> */}
</TableRow>
))}
</TableBody>
</Table>
</LstCard>
);
}

View File

@@ -0,0 +1,3 @@
export default function MaterialCheck() {
return <div>MaterialCheck</div>;
}

View File

@@ -0,0 +1,211 @@
import { LstCard } from "@/components/extendedUI/LstCard";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import { Popover, PopoverTrigger } from "@/components/ui/popover";
import { Skeleton } from "@/components/ui/skeleton";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useSessionStore } from "@/lib/store/sessionStore";
import { useModuleStore } from "@/lib/store/useModuleStore";
import { cn } from "@/lib/utils";
import { PopoverContent } from "@radix-ui/react-popover";
import { Link, useRouter } from "@tanstack/react-router";
import { startOfMonth } from "date-fns";
import { format } from "date-fns-tz";
import { CalendarIcon } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
import KFP from "../KFP";
export default function MaterialData() {
const { modules } = useModuleStore();
const { user } = useSessionStore();
const router = useRouter();
const [date, setDate] = useState<Date>();
if (!user) {
router.navigate({ to: "/" });
}
const eomMod = modules.filter((m) => m.name === "eom");
// the users current role for eom is?
const role: any =
user?.roles.filter((r) => r.module_id === eomMod[0].module_id) || "";
const tabs = [
{
key: "kfp",
label: "Key Figures",
roles: ["admin", "systemAdmin"],
content: <KFP />,
},
{
key: "fg",
label: "Finished Goods",
roles: ["admin", "systemAdmin"],
content: <DummyContent />,
},
{
key: "mm",
label: "Main Material",
roles: ["admin", "systemAdmin"],
content: <DummyContent />,
},
{
key: "mb",
label: "Master Batch",
roles: ["admin", "systemAdmin"],
content: <DummyContent />,
},
{
key: "ab",
label: "Additive",
roles: ["admin", "systemAdmin"],
content: <DummyContent />,
},
{
key: "pp",
label: "Purchased Preforms",
roles: ["admin", "systemAdmin"],
content: <DummyContent />,
},
{
key: "pre",
label: "Preforms",
roles: ["admin", "systemAdmin"],
content: <DummyContent />,
},
{
key: "pkg",
label: "Packaging",
roles: ["admin", "systemAdmin"],
content: <DummyContent />,
},
{
key: "ui",
label: "Undefined Items",
roles: ["admin"],
content: <DummyContent />,
},
];
return (
<div className="m-2 w-screen">
<div className="mb-2 flex flex-row">
<Popover>
<PopoverTrigger asChild>
<Button
variant={"outline"}
className={cn(
"w-[280px] justify-start text-left font-normal",
!date && "text-muted-foreground"
)}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{date ? (
format(date, "PPP")
) : (
<span>Pick a date</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar
mode="single"
selected={date}
onSelect={setDate}
initialFocus
/>
</PopoverContent>
</Popover>
<div className="ml-2">
<Button
onClick={() =>
toast.success(
`Getting data for ${startOfMonth(date!)}-${date}`
)
}
>
<span className="text-sm">Update Data</span>
</Button>
</div>
</div>
<Tabs defaultValue="mm">
<TabsList>
{tabs.map((tab) => {
if (tab.roles.includes(role[0].role))
return (
<TabsTrigger value={tab.key}>
{tab.label}
</TabsTrigger>
);
})}
</TabsList>
{tabs.map((tab) => {
if (tab.roles.includes(role[0].role))
return (
<TabsContent value={tab.key}>
{tab.content}
</TabsContent>
);
})}
</Tabs>
</div>
);
}
function DummyContent() {
return (
<LstCard className="w-5/6">
<Table>
<TableHeader>
<TableRow>
<TableHead>Av</TableHead>
<TableHead>Description</TableHead>
<TableHead>Material Type</TableHead>
<TableHead>Waste</TableHead>
<TableHead>Loss / Gain $$</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{Array(10)
.fill(0)
.map((_, i) => (
<TableRow key={i}>
<TableCell className="font-medium m-2">
<Link
to="/article/$av"
params={{ av: `${i}` }}
>
{i}
</Link>
</TableCell>
<TableCell className="font-medium">
<Skeleton className="h-4" />
</TableCell>
<TableCell>
<Skeleton className="h-4" />
</TableCell>
<TableCell>
<Skeleton className="h-4" />
</TableCell>
<TableCell>
<Skeleton className="h-4" />
</TableCell>
{/* <TableCell>
<Skeleton className="h-4" />
</TableCell> */}
</TableRow>
))}
</TableBody>
</Table>
</LstCard>
);
}

View File

@@ -0,0 +1,449 @@
import { LstCard } from "@/components/extendedUI/LstCard";
import { Button } from "@/components/ui/button";
import { CardContent, CardHeader } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useAppForm } from "@/utils/formStuff";
import axios from "axios";
import { useState } from "react";
import { toast } from "sonner";
import { Info } from "lucide-react";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
export default function TransferToNextLot() {
const [gaylordFilled, setGaylordFilled] = useState([0]);
const [actualAmount, setActualAmount] = useState(0);
const [tab, setTab] = useState("esitmate");
const [typeSwitch, setTypeSwitch] = useState(false);
const form = useAppForm({
defaultValues: {
runnungNumber: "",
lotNumber: "",
originalAmount: "",
amount: "",
},
onSubmit: async ({ value }) => {
//console.log(transferData);
//toast.success("603468: qty: 361, was transfered to lot:24897");
try {
const res = await axios.post("/api/ocp/materiallottransfer", {
runnungNumber: Number(value.runnungNumber),
lotNumber: Number(value.lotNumber),
originalAmount: Number(value.originalAmount),
level: Number(
gaylordFilled.length === 1
? 0.25
: gaylordFilled.length === 2
? 0.5
: gaylordFilled.length === 3
? 0.75
: gaylordFilled.length === 4 && 0.95
),
amount: actualAmount,
type: typeSwitch ? "eom" : "lot",
});
if (res.data.success) {
toast.success(`${res.data.message}`);
form.reset();
setGaylordFilled([0]);
setActualAmount(0);
}
//console.log(res.data);
if (!res.data.success) {
toast.error(res.data.message);
}
} catch (error) {
if (error) {
console.log(error);
//toast.error(error)
}
}
},
});
return (
<div>
<LstCard>
<CardHeader>
<p className="text-center text-lg">
Material Transfer to Next lot
</p>
</CardHeader>
<div>
<div className="flex flex-wrap m-2 gap-2">
<div className="flex gap-2">
<div>
<LstCard className="">
<Tabs
defaultValue={tab}
onValueChange={setTab}
>
<TabsList>
<TabsTrigger value="esitmate">
Estimate Amount
</TabsTrigger>
<TabsTrigger value="actual">
Actual Amount
</TabsTrigger>
</TabsList>
<TabsContent value="esitmate">
<div className="grid columns-1">
<button
className={`box-border h-16 w-96 border-3 ${
gaylordFilled.includes(
4
)
? " bg-green-500"
: ""
}`}
onClick={() =>
setGaylordFilled([
1, 2, 3, 4,
])
}
>
<p className="text-center">
Almost full
</p>
</button>
<button
className={`box-border h-16 w-96 border-3 ${
gaylordFilled.includes(
3
)
? " bg-green-500"
: ""
}`}
onClick={() =>
setGaylordFilled([
1, 2, 3,
])
}
>
<p className="text-center">
About 75% full
</p>
</button>
<button
className={`box-border h-16 w-96 border-3 ${
gaylordFilled.includes(
2
)
? " bg-green-500"
: ""
}`}
onClick={() =>
setGaylordFilled([1, 2])
}
>
<p className="text-center">
Half full
</p>
</button>
<button
className={`box-border h-16 w-96 border-3 ${
gaylordFilled.includes(
1
)
? " bg-green-500"
: ""
}`}
onClick={() =>
setGaylordFilled(() => [
1,
])
}
>
<p className="text-center">
Almost empty
</p>
</button>
</div>
<div className="flex justify-end pr-1">
<Button
onClick={() =>
setGaylordFilled([0])
}
>
Reset Gaylord
</Button>
</div>
</TabsContent>
<TabsContent
value="actual"
className="w-96"
>
<CardHeader>
<p>
Enter the total amount of
the cage/gaylord
</p>
</CardHeader>
<CardContent>
<Input
type="number"
//placeholder="35"
onChange={(e) =>
setActualAmount(
Number(
e.target.value
)
)
}
/>
</CardContent>
</TabsContent>
</Tabs>
</LstCard>
</div>
<div>
<div className="w-96">
<LstCard>
<form
onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}
>
<div className="mt-3 p-2">
<form.AppField
name="runnungNumber"
children={(field) => (
<field.InputField
label="Runnung Number"
inputType="number"
required={true}
/>
)}
/>
</div>
<div className="mt-3 p-2">
<form.AppField
name="lotNumber"
children={(field) => (
<field.InputField
label="Lot Number"
inputType="number"
required={true}
/>
)}
/>
{tab !== "actual" && (
<div className="mt-3 p-2">
<form.AppField
name="originalAmount"
children={(
field
) => (
<field.InputField
label="Orignal Quantity"
inputType="number"
required={
true
}
/>
)}
/>
</div>
)}
</div>
<div className="flex justify-between p-2">
<div className="flex items-center space-x-2">
<Switch
checked={typeSwitch}
onCheckedChange={
setTypeSwitch
}
/>
<span>
{typeSwitch ? (
<div className="flex items-center space-x-2">
<span>
"EOM
Transfer"
</span>
<Tooltip>
<TooltipTrigger>
<Info className="h-[16px] w-[16px]" />
</TooltipTrigger>
<TooltipContent>
<p>
Click
the
toggle
if
you
will
be
transfering
at
EOM,
NOTE:
This
will
trigger
the
delayed
transfer.
</p>
</TooltipContent>
</Tooltip>
</div>
) : (
<div className="flex items-center space-x-2">
<span>
"Lot
Transfer"
</span>
<Tooltip>
<TooltipTrigger>
<Info className="h-[16px] w-[16px]" />
</TooltipTrigger>
<TooltipContent>
<p>
Click
the
toggle
if
you
will
be
transfering
at
EOM,
NOTE:
This
will
trigger
the
delayed
transfer.
</p>
</TooltipContent>
</Tooltip>
</div>
)}
</span>
</div>
<form.AppForm>
<form.SubmitButton>
Transfer To Lot
</form.SubmitButton>
</form.AppForm>
</div>
</form>
</LstCard>
</div>
</div>
</div>
<div>
<LstCard className="p-2">
<CardHeader>
<p className="text-center text-lg">
Moving material to the next lot.
</p>
</CardHeader>
{tab !== "actual" ? (
<div>
<ol>
<li>
1. Grab the gaylord running
number from the gaylord at the
line/next to the tschritter
</li>
<li>
2. Grab the next lot number you
are going to be running (or the
one that state no Main material
prepared)
</li>
<li>
3. Enter the total gaylord
weight (this is how much the
gaylord weighed when it came in
from the supplier.)
</li>
<li>
4. *Click the level of the
gaylord (this is just an
estimate to move to the next
lot.)
</li>
<li>
5. type in running number on the
gaylord.
</li>
<li>
6. Type in the new lot number.
</li>
<li>7. Press "Transfer To Lot"</li>
</ol>
<br></br>
<p>
* to reduce the time needed to get
the lot going we will use an
estimate of how full the gaylord is.
</p>
<p>
NOTE: This is not the return
process, this process will just get
the gaylord to the next lot.
</p>
</div>
) : (
<div>
<ol>
<li>
1. Grab the gaylord running
number from the gaylord at the
line/next to the tschritter
</li>
<li>
2. Grab the next lot number you
are going to be running (or the
one that state no Main material
prepared)
</li>
<li>
3. Take the gaylord to the scale
and weight it
</li>
<li>
4. Enter the weight of the
gaylord minus the tar weight.
</li>
<li>
5. type in running number on the
gaylord.
</li>
<li>
6. Type in the new lot number.
</li>
<li>7. Press "Transfer To Lot"</li>
</ol>
<br></br>
<p>
NOTE: This is not the return
process, this process will just get
the gaylord to the next lot.
</p>
</div>
)}
</LstCard>
</div>
</div>
</div>
</LstCard>
</div>
);
}

View File

@@ -40,7 +40,6 @@ export function AttachSilo(props: any) {
machineId: "", machineId: "",
}, },
onSubmit: async ({ value }) => { onSubmit: async ({ value }) => {
console.log(value);
try { try {
const res = await axios.post( const res = await axios.post(
"/api/logistics/attachsilo", "/api/logistics/attachsilo",

View File

@@ -0,0 +1,29 @@
import * as React from "react"
import * as SwitchPrimitive from "@radix-ui/react-switch"
import { cn } from "@/lib/utils"
function Switch({
className,
...props
}: React.ComponentProps<typeof SwitchPrimitive.Root>) {
return (
<SwitchPrimitive.Root
data-slot="switch"
className={cn(
"peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
>
<SwitchPrimitive.Thumb
data-slot="switch-thumb"
className={cn(
"bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
)}
/>
</SwitchPrimitive.Root>
)
}
export { Switch }

View File

@@ -1,5 +1,6 @@
import ConsumeMaterial from "@/components/logistics/materialHelper/consumption/ConsumeMaterial"; import ConsumeMaterial from "@/components/logistics/materialHelper/consumption/ConsumeMaterial";
import PreformReturn from "@/components/logistics/materialHelper/consumption/MaterialReturn"; import PreformReturn from "@/components/logistics/materialHelper/consumption/MaterialReturn";
import TransferToNextLot from "@/components/logistics/materialHelper/consumption/TransferToNextLot";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute( export const Route = createFileRoute(
@@ -21,10 +22,18 @@ export const Route = createFileRoute(
function RouteComponent() { function RouteComponent() {
const url: string = window.location.host.split(":")[0]; const url: string = window.location.host.split(":")[0];
const auth = localStorage.getItem("auth_token");
return ( return (
<div> <div className="flex flex-wrap">
<ConsumeMaterial /> {auth ? (
{url === "localhost" && <PreformReturn />} <>
<ConsumeMaterial />
{url === "localhost" && <PreformReturn />}
<TransferToNextLot />
</>
) : (
<TransferToNextLot />
)}
</div> </div>
); );
} }

164
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "lstv2", "name": "lstv2",
"version": "2.24.1", "version": "2.25.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "lstv2", "name": "lstv2",
"version": "2.24.1", "version": "2.25.0",
"dependencies": { "dependencies": {
"@dotenvx/dotenvx": "^1.45.1", "@dotenvx/dotenvx": "^1.45.1",
"@hono/node-server": "^1.14.4", "@hono/node-server": "^1.14.4",
@@ -29,6 +29,7 @@
"fast-xml-parser": "^5.2.5", "fast-xml-parser": "^5.2.5",
"fs-extra": "^11.3.0", "fs-extra": "^11.3.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"morgan": "^1.10.1",
"mssql": "^11.0.1", "mssql": "^11.0.1",
"nodemailer": "^7.0.3", "nodemailer": "^7.0.3",
"nodemailer-express-handlebars": "^7.0.0", "nodemailer-express-handlebars": "^7.0.0",
@@ -1548,14 +1549,6 @@
"url": "https://paulmillr.com/funding/" "url": "https://paulmillr.com/funding/"
} }
}, },
"node_modules/@petamoriken/float16": {
"version": "3.9.2",
"resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.9.2.tgz",
"integrity": "sha512-VgffxawQde93xKxT3qap3OH+meZf7VaSB5Sqd4Rqc+FP5alWbpOyan/7tRbOAvynjpG3GpdtAuGU/NdhQpmrog==",
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/@scalar/core": { "node_modules/@scalar/core": {
"version": "0.3.3", "version": "0.3.3",
"resolved": "https://registry.npmjs.org/@scalar/core/-/core-0.3.3.tgz", "resolved": "https://registry.npmjs.org/@scalar/core/-/core-0.3.3.tgz",
@@ -2118,6 +2111,24 @@
], ],
"license": "MIT" "license": "MIT"
}, },
"node_modules/basic-auth": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
"license": "MIT",
"dependencies": {
"safe-buffer": "5.1.2"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/basic-auth/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT"
},
"node_modules/bcryptjs": { "node_modules/bcryptjs": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz", "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz",
@@ -3379,6 +3390,15 @@
"node": ">=0.4.0" "node": ">=0.4.0"
} }
}, },
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/detect-file": { "node_modules/detect-file": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
@@ -3716,6 +3736,12 @@
"node": ">=16" "node": ">=16"
} }
}, },
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
},
"node_modules/emoji-regex": { "node_modules/emoji-regex": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -4227,70 +4253,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/gel": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/gel/-/gel-2.0.1.tgz",
"integrity": "sha512-gfem3IGvqKqXwEq7XseBogyaRwGsQGuE7Cw/yQsjLGdgiyqX92G1xENPCE0ltunPGcsJIa6XBOTx/PK169mOqw==",
"license": "Apache-2.0",
"optional": true,
"peer": true,
"dependencies": {
"@petamoriken/float16": "^3.8.7",
"debug": "^4.3.4",
"env-paths": "^3.0.0",
"semver": "^7.6.2",
"shell-quote": "^1.8.1",
"which": "^4.0.0"
},
"bin": {
"gel": "dist/cli.mjs"
},
"engines": {
"node": ">= 18.0.0"
}
},
"node_modules/gel/node_modules/env-paths": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
"integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==",
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/gel/node_modules/isexe": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz",
"integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==",
"license": "ISC",
"optional": true,
"peer": true,
"engines": {
"node": ">=16"
}
},
"node_modules/gel/node_modules/which": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz",
"integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==",
"license": "ISC",
"optional": true,
"peer": true,
"dependencies": {
"isexe": "^3.1.1"
},
"bin": {
"node-which": "bin/which.js"
},
"engines": {
"node": "^16.13.0 || >=18.0.0"
}
},
"node_modules/get-caller-file": { "node_modules/get-caller-file": {
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@@ -6080,6 +6042,37 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/morgan": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz",
"integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==",
"license": "MIT",
"dependencies": {
"basic-auth": "~2.0.1",
"debug": "2.6.9",
"depd": "~2.0.0",
"on-finished": "~2.3.0",
"on-headers": "~1.1.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/morgan/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/morgan/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -6236,6 +6229,27 @@
"node": ">=14.0.0" "node": ">=14.0.0"
} }
}, },
"node_modules/on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
"license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/on-headers": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/once": { "node_modules/once": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -7396,7 +7410,7 @@
"version": "1.8.2", "version": "1.8.2",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz",
"integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==",
"devOptional": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"

View File

@@ -1,6 +1,6 @@
{ {
"name": "lstv2", "name": "lstv2",
"version": "2.24.1", "version": "2.25.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "concurrently -n \"server,frontend\" -c \"#007755,#2f6da3\" \"npm run dev:server\" \"cd frontend && npm run dev\"", "dev": "concurrently -n \"server,frontend\" -c \"#007755,#2f6da3\" \"npm run dev:server\" \"cd frontend && npm run dev\"",
@@ -36,7 +36,7 @@
} }
}, },
"admConfig": { "admConfig": {
"build": 548, "build": 575,
"oldBuild": "backend-0.1.3.zip" "oldBuild": "backend-0.1.3.zip"
}, },
"devDependencies": { "devDependencies": {
@@ -78,6 +78,7 @@
"fast-xml-parser": "^5.2.5", "fast-xml-parser": "^5.2.5",
"fs-extra": "^11.3.0", "fs-extra": "^11.3.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"morgan": "^1.10.1",
"mssql": "^11.0.1", "mssql": "^11.0.1",
"nodemailer": "^7.0.3", "nodemailer": "^7.0.3",
"nodemailer-express-handlebars": "^7.0.0", "nodemailer-express-handlebars": "^7.0.0",

View File

@@ -6,17 +6,43 @@ import stats from "./route/stats.js";
import history from "./route/invHistory.js"; import history from "./route/invHistory.js";
import { createJob } from "../notifications/utils/processNotifications.js"; import { createJob } from "../notifications/utils/processNotifications.js";
import { historicalInvIMmport } from "./utils/historicalInv.js"; import { historicalInvIMmport } from "./utils/historicalInv.js";
import { tryCatch } from "../../globalUtils/tryCatch.js";
import { query } from "../sqlServer/prodSqlServer.js";
import { shiftChange } from "../sqlServer/querys/misc/shiftChange.js";
import { createLog } from "../logger/logger.js";
const routes = [stats, history] as const; const routes = [stats, history] as const;
const appRoutes = routes.forEach((route) => { const appRoutes = routes.forEach((route) => {
app.route("/eom", route); app.route("/eom", route);
}); });
// setTimeout(() => { setTimeout(async () => {
// historicalInvIMmport(); const { data: shift, error: shiftError } = (await tryCatch(
// }, 5 * 1000); query(shiftChange, "shift change from material.")
)) as any;
if (shiftError) {
createLog(
"error",
"eom",
"eom",
"There was an error getting the shift times will use fallback times"
);
}
// shift split
const shiftTimeSplit = shift?.data[0]?.shiftChange.split(":");
const cronSetup = `${
shiftTimeSplit.length > 0 ? `${parseInt(shiftTimeSplit[1])}` : "0"
} ${
shiftTimeSplit.length > 0 ? `${parseInt(shiftTimeSplit[0])}` : "7"
} * * *`;
//console.log(cronSetup);
createJob("eom_historical_inv", cronSetup, historicalInvIMmport);
}, 5 * 1000);
// the time we want to run the hostircal data should be the same time the historical data run on the server // the time we want to run the hostircal data should be the same time the historical data run on the server
// getting this from the shift time // getting this from the shift time
createJob("eom_historical_inv", "0 7 * * *", historicalInvIMmport);
export default app; export default app;

View File

@@ -157,7 +157,7 @@ export const standardOrders = async (data: any, user: any) => {
deliveryDate: excelDateStuff(o.DeliveryDate), deliveryDate: excelDateStuff(o.DeliveryDate),
customerLineItemNo: o.CustomerLineNumber, // this is how it is currently sent over from abbott customerLineItemNo: o.CustomerLineNumber, // this is how it is currently sent over from abbott
customerReleaseNo: o.CustomerRealeaseNumber, // same as above customerReleaseNo: o.CustomerRealeaseNumber, // same as above
remark: o.remark === "" ? null : o.remark, remark: o.Remark === "" ? null : o.Remark,
}, },
], ],
}; };
@@ -169,7 +169,7 @@ export const standardOrders = async (data: any, user: any) => {
orders: [...predefinedObject.orders, ...nOrder], orders: [...predefinedObject.orders, ...nOrder],
}; };
//console.log(updatedPredefinedObject); //console.log(updatedPredefinedObject.orders[0]);
// post the orders to the server // post the orders to the server
const posting: any = await postOrders(updatedPredefinedObject, user); const posting: any = await postOrders(updatedPredefinedObject, user);

View File

@@ -17,6 +17,8 @@ export const consumeMaterial = async (data: Data, prod: any) => {
const { runningNr, lotNum } = data; const { runningNr, lotNum } = data;
// replace the rn // replace the rn
console.log(data);
const rnReplace = labelData.replaceAll("[rn]", runningNr); const rnReplace = labelData.replaceAll("[rn]", runningNr);
let barcode; let barcode;

View File

@@ -239,15 +239,16 @@ export const tiImport = async () => {
.replaceAll("[customerState]", orderData[0].city.split(",")[1]) .replaceAll("[customerState]", orderData[0].city.split(",")[1])
.replaceAll("[customerZip]", orderData[0].zipCode) .replaceAll("[customerZip]", orderData[0].zipCode)
.replaceAll("[customerPO]", orderData[0].Header) .replaceAll("[customerPO]", orderData[0].Header)
.replaceAll( // .replaceAll(
"[glCoding]", // "[glCoding]",
`52410-${ // `52410-${
orderData[0].artileType.toLowerCase() === "preform" || // orderData[0].artileType.toLowerCase() === "preform" ||
orderData[0].artileType.toLowerCase() === "metalCage" // orderData[0].artileType.toLowerCase() === "metalCage"
? 31 // ? 31
: plantI[0].greatPlainsPlantCode // : plantI[0].greatPlainsPlantCode
}` // }`
) // {"52410 - " + (artileType.toLowerCase() === "preform" || artileType.toLowerCase() === "metalCage" ? 31: plantInfo[0].greatPlainsPlantCode)} // ) // {"52410 - " + (artileType.toLowerCase() === "preform" || artileType.toLowerCase() === "metalCage" ? 31: plantInfo[0].greatPlainsPlantCode)}
.replaceAll("[glCoding]", `52410`)
.replaceAll( .replaceAll(
"[pfc]", "[pfc]",
`${ `${
@@ -257,6 +258,15 @@ export const tiImport = async () => {
: orderData[0].costCenter : orderData[0].costCenter
}` }`
) )
.replaceAll(
"[locCode]",
`${
orderData[0].artileType.toLowerCase() === "preform" ||
orderData[0].artileType.toLowerCase() === "metalCage"
? 31
: plantI[0].greatPlainsPlantCode
}`
)
.replaceAll("[priceSheet]", await scacCheck(orderData)); .replaceAll("[priceSheet]", await scacCheck(orderData));
//send over to be processed //send over to be processed
@@ -276,16 +286,28 @@ export const tiImport = async () => {
* Update the db so we dont try to pull the next one * Update the db so we dont try to pull the next one
*/ */
const currentDate = new Date(Date.now());
const uniqueOrders = Array.from( const uniqueOrders = Array.from(
new Set([ new Set([
...notiSet[0].notifiySettings.releases, ...notiSet[0].notifiySettings.releases,
{ {
releaseNumber: header[0].releaseNumber, releaseNumber: header[0].releaseNumber,
timeStamp: new Date(Date.now()), timeStamp: currentDate,
}, },
]) ])
); );
// 45 days ago
const dateLimit = new Date(
currentDate.getTime() - 45 * 24 * 60 * 60 * 1000
);
// filter dates
let filteredOrders = uniqueOrders.filter((item) => {
const time = new Date(item.timeStamp).getTime();
return time >= dateLimit.getTime();
});
const { data, error } = await tryCatch( const { data, error } = await tryCatch(
db db
.update(notifications) .update(notifications)
@@ -293,7 +315,7 @@ export const tiImport = async () => {
lastRan: sql`NOW()`, lastRan: sql`NOW()`,
notifiySettings: { notifiySettings: {
...notiSet[0].notifiySettings, ...notiSet[0].notifiySettings,
releases: uniqueOrders, releases: filteredOrders,
}, },
}) })
.where(eq(notifications.name, "tiIntergration")) .where(eq(notifications.name, "tiIntergration"))

View File

@@ -110,8 +110,11 @@ export let xmlPayloadTI = `
<ReferenceNumber type="Shipment Number" isPrimary="true">[shipNumber]</ReferenceNumber> <ReferenceNumber type="Shipment Number" isPrimary="true">[shipNumber]</ReferenceNumber>
<ReferenceNumber type="PO Number" isPrimary="false">[customerPO]</ReferenceNumber> <ReferenceNumber type="PO Number" isPrimary="false">[customerPO]</ReferenceNumber>
[multieReleaseNumber] [multieReleaseNumber]
<ReferenceNumber type="Store Number" isPrimary="false">[glCoding]</ReferenceNumber> <!-- Comments here -->
<ReferenceNumber type="Profit Center" isPrimary="false">[pfc]</ReferenceNumber> <!-- <ReferenceNumber type="Store Number" isPrimary="false">[glCoding]</ReferenceNumber> -->
<ReferenceNumber type="GL Account Code" isPrimary="false">[glCoding]</ReferenceNumber>
<ReferenceNumber type="Profit Center" isPrimary="false">[pfc]</ReferenceNumber>
<ReferenceNumber type="Location Code" isPrimary="false">[locCode]</ReferenceNumber>
</ReferenceNumbers> </ReferenceNumbers>
<Services/> <Services/>
<EquipmentList/> <EquipmentList/>

View File

@@ -68,8 +68,8 @@ export const note: any = [
{ {
name: "tiIntergration", name: "tiIntergration",
description: "Checks for new releases to be put into ti", description: "Checks for new releases to be put into ti",
checkInterval: 2, checkInterval: 60,
timeType: "hour", timeType: "min",
emails: "", emails: "",
active: false, active: false,
notifiySettings: { notifiySettings: {

View File

@@ -96,7 +96,7 @@ export const bookInLabel = async (data: any) => {
"ocp", "ocp",
`${ `${
data.printer ? data.printer[0].name : "Manual book in" data.printer ? data.printer[0].name : "Manual book in"
}, "Error: ${error}` }, "Error: ${error?.response.data}`
); );
// console.log(error.response.data); // console.log(error.response.data);
return { return {

View File

@@ -19,11 +19,18 @@ interface Printer {
// Add any other expected properties // Add any other expected properties
} }
interface Zechetti {
line: string;
printer: number;
printerName: string;
}
export const labelingProcess = async ({ export const labelingProcess = async ({
line = null as string | null, line = null as string | null,
printer = null as Printer | null, printer = null as Printer | null,
userPrinted = null, userPrinted = null,
rfidTag = null, rfidTag = null,
zechette = null as Zechetti | null,
} = {}) => { } = {}) => {
/** /**
* Creates a label once all logic is passed * Creates a label once all logic is passed
@@ -69,6 +76,34 @@ export const labelingProcess = async ({
} }
} }
// if we are running the zechettii
if (zechette) {
const macId = await getMac(zechette.line);
// filter out the lot for the line
filteredLot = lots.data.filter(
(l: any) => l.MachineID === macId[0]?.HumanReadableId
);
if (filteredLot.length === 0) {
createLog(
"error",
"labeling",
"ocp",
`There is not a lot assigned to ${line}.`
);
return {
success: false,
message: `There is not a lot assigned to ${line}.`,
};
}
// remap the printer so its the zechetti one
filteredLot = filteredLot.map((p: any) => ({
...p,
printerID: zechette.printer,
PrinterName: zechette.printerName,
}));
}
// if we came from a printer // if we came from a printer
if (printer) { if (printer) {
// filter the lot based on the printerID // filter the lot based on the printerID
@@ -170,20 +205,15 @@ export const labelingProcess = async ({
}; };
} }
// check mm is good // check the material... mm,color (auto and manual combined), pkg
const mmStaged = await isMainMatStaged(filteredLot[0]); const mmStaged = await isMainMatStaged(filteredLot[0]);
if (!mmStaged) { if (!mmStaged.success) {
createLog( createLog("error", "labeling", "ocp", mmStaged.message);
"error",
"labeling",
"ocp",
`Main material is not prepaired for lot ${filteredLot[0].lot}`
);
return { return {
success: false, success: false,
message: `Main material is not prepaired for lot ${filteredLot[0].lot}`, message: mmStaged.message,
}; };
} }
@@ -247,6 +277,20 @@ export const labelingProcess = async ({
let book: any = []; let book: any = [];
if (bookin[0].value === "1") { if (bookin[0].value === "1") {
book = await bookInLabel(label.data); book = await bookInLabel(label.data);
if (!book.success) {
// createLog(
// "error",
// "labeling",
// "ocp",
// `Error Booking in label: ${book.errors[0].message}`
// );
return {
success: false,
message: `Error Booking in label: ${book.errors[0]?.message}`,
data: book,
};
}
} else { } else {
createLog("info", "labeling", "ocp", "Bookin is turned off."); createLog("info", "labeling", "ocp", "Bookin is turned off.");

View File

@@ -0,0 +1,426 @@
import { eq } from "drizzle-orm";
import { db } from "../../../../../database/dbclient.js";
import { printerData } from "../../../../../database/schema/printers.js";
import { runProdApi } from "../../../../globalUtils/runProdApi.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { createLog } from "../../../logger/logger.js";
import { query } from "../../../sqlServer/prodSqlServer.js";
import { labelInfo } from "../../../sqlServer/querys/warehouse/labelInfo.js";
import { format, formatDuration, intervalToDuration } from "date-fns";
import { shiftChange } from "../../../sqlServer/querys/misc/shiftChange.js";
import { success } from "zod/v4";
type NewLotData = {
runningNumber: number;
lotNumber: number;
originalAmount: number;
level: number;
amount: number;
type: "lot" | "eom";
};
interface PendingJob {
timeoutId: NodeJS.Timeout;
runningNumber: string | number;
data: any;
consumeLot: any;
newQty: any;
scheduledFor: Date;
}
export const pendingJobs = new Map<string | number, PendingJob>();
/**
* Move manual material to a new lot.
*
* The data sent over should be
* Running number
* Lot number
* Orignal Quantity
* level of gaylord
* amount can be sent over as a precise amount
* type what way are we lots
*/
export const lotMaterialTransfer = async (data: NewLotData) => {
// check if we already have this running number scheduled
if (pendingJobs.has(data.runningNumber)) {
const job = pendingJobs.get(data.runningNumber) as PendingJob;
const duration = intervalToDuration({
start: new Date(),
end: job.scheduledFor,
});
createLog(
"error",
"materials",
"ocp",
`${
data.runningNumber
} is pending to be transfered already, remaining time ${formatDuration(
duration,
{ format: ["hours", "minutes", "seconds"] }
)}`
);
return {
success: false,
message: `${
data.runningNumber
} is pending to be transfered already, remaining time ${formatDuration(
duration,
{ format: ["hours", "minutes", "seconds"] }
)}`,
data: [],
};
}
// get the shift time
const { data: shift, error: shiftError } = (await tryCatch(
query(shiftChange, "shift change from material.")
)) as any;
if (shiftError) {
createLog(
"error",
"materials",
"ocp",
"There was an error getting the shift times will use fallback times"
);
}
// shift split
const shiftTimeSplit = shift?.data[0]?.shiftChange.split(":");
//console.log(shiftTimeSplit);
// Current time
const now = new Date();
// Target time: today at 06:35
const target = new Date(
now.getFullYear(),
now.getMonth(),
1, //now.getDate(),
shiftTimeSplit.length > 0 ? parseInt(shiftTimeSplit[0]) - 1 : 5, // this will parse the hour to remove teh zero
shiftTimeSplit.length > 0 ? parseInt(shiftTimeSplit[1]) + 3 : 3,
0,
0
);
// to early time
const early = new Date(
now.getFullYear(),
now.getMonth(),
1, //now.getDate(),
shiftTimeSplit.length > 0 ? parseInt(shiftTimeSplit[0]) - 1 : 5, // this will parse the hour to remove teh zero
shiftTimeSplit.length > 0 ? parseInt(shiftTimeSplit[1]) : 0,
0,
0
);
// next month just to be here
const nextMonth = new Date(
now.getFullYear(),
now.getMonth() + 1,
1, //now.getDate(),
shiftTimeSplit.length > 0 ? parseInt(shiftTimeSplit[0]) - 1 : 5, // this will parse the hour to remove teh zero
shiftTimeSplit.length > 0 ? parseInt(shiftTimeSplit[1]) : 0,
0,
0
);
// console.log(early, target);
// if we are to early return early only if we are sending over eom
if (data.type === "eom" && (early > now || target < now)) {
createLog(
"error",
"materials",
"ocp",
`Eom transfers is not allowed right now please try again at ${format(
nextMonth,
"M/d/yyyy hh:mm"
)} `
);
return {
success: false,
message: `Eom transfers is not allowed right now please try again at ${format(
nextMonth,
"M/d/yyyy hh:mm"
)} `,
data: [],
};
}
let timeoutTrans: number = data.type === "lot" ? 30 : 10;
// get the barcode, and layoutID from the running number
const { data: label, error: labelError } = (await tryCatch(
query(
labelInfo.replace("[runningNr]", `${data.runningNumber}`),
"Get label info"
)
)) as any;
if (labelError) {
createLog(
"error",
"materials",
"ocp",
"There was an error getting the label info"
);
return {
success: false,
message: "There was an error getting the label info",
data: labelError,
};
}
if (label.data.length === 0) {
createLog(
"error",
"materials",
"ocp",
`${data.runningNumber}: dose not exist or no longer in stock.`
);
return {
success: false,
message: `${data.runningNumber}: dose not exist or no longer in stock.`,
data: [],
};
}
//console.log(label);
if (label.data[0]?.stockStatus === "onStock") {
createLog(
"error",
"materials",
"ocp",
`${data.runningNumber}: currently in stock and not consumed to a lot.`
);
return {
success: false,
message: `${data.runningNumber}: currently in stock and not consumed to a lot.`,
data: [],
};
}
// get the pdf24 printer id
const { data: printer, error: printerError } = (await tryCatch(
db.select().from(printerData).where(eq(printerData.name, "PDF24"))
)) as any;
if (printerError) {
createLog(
"error",
"materials",
"ocp",
"There was an error the printer info"
);
return {
success: false,
message: "There was an error the printer info",
data: printerError,
};
}
// calculate the remaining amount bascially it will be orignal number * level sent over
// level should be sent in a decimal .25 .5 .75 .95 the 95 will allow basically the what looks to be a full gaylord but we always want to consume something
const newQty =
data.amount > 0
? data.amount
: (data.originalAmount * data.level).toFixed(0);
//console.log(data.amount);
// reprint the label and send it to pdf24
const reprintData = {
clientId: 999,
runningNo: label?.data[0].runnungNumber,
printerId: printer[0].humanReadableId,
layoutId: label?.data[0].labelLayout,
noOfCopies: 0,
quantity: newQty,
} as any;
//console.log(reprintData);
const { data: reprint, error: reprintError } = (await tryCatch(
runProdApi({
endpoint: "/public/v1.0/ProductionLabelling/ReprintLabel",
data: [reprintData],
})
)) as any;
if (!reprint.success) {
createLog(
"error",
"materials",
"ocp",
`RN:${data.runningNumber}, Reprinting Error: ${reprint.data.data.message}`
);
return {
success: false,
message: `RN:${data.runningNumber}, Reprinting Error: ${reprint.data.data.message}`,
data: reprint,
};
}
// return the label back to fm1 lane id 10001
const matReturnData = {
barcode: label?.data[0].Barcode,
laneId: 10001,
};
//console.log(matReturnData);
const { data: matReturn, error: matReturError } = (await tryCatch(
runProdApi({
endpoint:
"/public/v1.0/IssueMaterial/ReturnPartiallyConsumedManualMaterial",
data: [matReturnData],
})
)) as any;
if (!matReturn.success) {
createLog(
"error",
"materials",
"ocp",
`RN:${data.runningNumber}, Return Error ${matReturn.data.data.errors[0].message}`
);
return {
success: false,
message: `RN:${data.runningNumber}, Return Error ${matReturn.data.data.errors[0].message}`,
data: matReturn,
};
}
// consume to the lot provided.
const consumeLot = {
productionLot: data.lotNumber,
barcode: label?.data[0].Barcode,
};
const delay =
data.type === "lot"
? timeoutTrans * 1000
: target.getTime() - now.getTime();
const transfer = await transferMaterial(delay, data, consumeLot, newQty);
if (!transfer.success) {
return {
success: transfer.success,
message: transfer.message,
data: transfer.data,
};
}
const duration = intervalToDuration({ start: now, end: target });
const pretty = formatDuration(duration, {
format: ["hours", "minutes", "seconds"],
});
if (data.type === "eom") {
return {
success: true,
message: `RN:${data.runningNumber}: qty: ${newQty}, will be transfered to lot: ${data.lotNumber}, in ${pretty} `,
data: [],
};
} else {
return {
success: true,
message: `RN:${data.runningNumber}: qty: ${newQty}, was transfered to lot: ${data.lotNumber}`,
data: [],
};
}
};
const transferMaterial = async (
delay: number,
data: any,
consumeLot: any,
newQty: any
) => {
//console.log(data);
if (pendingJobs.has(data.runningNumber)) {
createLog(
"error",
"materials",
"ocp",
`${data.runningNumber} is pending to be transfered already`
);
return {
success: false,
message: `${data.runningNumber} is pending to be transfered already`,
data: [],
};
}
const scheduledFor = new Date(Date.now() + delay);
// sets the time out based on the type of transfer sent over.
const timeoutId = setTimeout(async () => {
try {
const { data: matConsume, error: matConsumeError } =
(await tryCatch(
runProdApi({
endpoint:
"/public/v1.0/IssueMaterial/ConsumeNonPreparedManualMaterial",
data: [consumeLot],
})
)) as any;
if (!matConsume?.success) {
createLog(
"error",
"materials",
"ocp",
`RN:${data.runningNumber}, Consume Error ${
matConsume?.data?.data?.errors?.[0]?.message ??
"Unknown"
}`
);
return; // still hits finally
}
createLog(
"info",
"materials",
"ocp",
`RN:${data.runningNumber}: qty: ${newQty}, was transferred to lot:${data.lotNumber}`
);
} catch (err) {
createLog(
"error",
"materials",
"ocp",
`RN:${data.runningNumber}, ${err}`
);
} finally {
// Always clear the pending entry, even if error
pendingJobs.delete(data.runningNumber);
}
}, delay);
pendingJobs.set(data.runningNumber, {
timeoutId,
runningNumber: data.runningNumber,
data,
consumeLot,
newQty,
scheduledFor,
});
// Immediately say we scheduled it
return {
success: true,
message: `Transfer for ${data.runningNumber} scheduled`,
data: [],
};
};
// setInterval(() => {
// console.log(pendingJobs);
// }, 5000);
// setTimeout(async () => {
// lotMaterialTransfer({
// runnungNumber: 603468,
// lotNumber: 24897,
// originalAmount: 380,
// level: 0.95,
// });
// }, 5000);

View File

@@ -1,12 +1,14 @@
import { tryCatch } from "../../../../globalUtils/tryCatch.js"; import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { createLog } from "../../../logger/logger.js"; import { createLog } from "../../../logger/logger.js";
import { serverSettings } from "../../../server/controller/settings/getSettings.js";
import { query } from "../../../sqlServer/prodSqlServer.js"; import { query } from "../../../sqlServer/prodSqlServer.js";
import { machineCheck } from "../../../sqlServer/querys/ocp/machineId.js"; import { machineCheck } from "../../../sqlServer/querys/ocp/machineId.js";
import { mmQuery } from "../../../sqlServer/querys/ocp/mainMaterial.js"; import { mmQuery } from "../../../sqlServer/querys/ocp/mainMaterial.js";
export const isMainMatStaged = async (lot: any) => { export const isMainMatStaged = async (lot: any) => {
const set = serverSettings.length === 0 ? [] : serverSettings;
// make staged false by deefault and error logged if theres an issue // make staged false by deefault and error logged if theres an issue
let isStaged = false; let isStaged = { message: "Material is staged", success: true };
const { data, error } = (await tryCatch( const { data, error } = (await tryCatch(
query( query(
@@ -26,7 +28,10 @@ export const isMainMatStaged = async (lot: any) => {
"ocp", "ocp",
`The machine dose not require mm to print and book in.` `The machine dose not require mm to print and book in.`
); );
return true; return {
message: "Machine dose not require material to be staged",
success: true,
};
} }
// strangly the lot is not always sending over in slc so adding this in for now to see what line is cauing this issue // strangly the lot is not always sending over in slc so adding this in for now to see what line is cauing this issue
@@ -51,20 +56,101 @@ export const isMainMatStaged = async (lot: any) => {
const res: any = r.data; const res: any = r.data;
createLog( // if (res[0].Staged >= 1) {
"info",
"mainMaterial",
"ocp",
`MainMaterial results: ${JSON.stringify(res)}`
);
if (res[0].Staged >= 1) {
isStaged = true;
}
// if (res[0].noShortage === "good") {
// isStaged = true; // isStaged = true;
// } // }
createLog("info", "mainMaterial", "ocp", `Maint material query ran.`);
const mainMateiral = res.filter((n: any) => n.IsMainMaterial);
if (mainMateiral[0]?.noMaterialShortage === "no") {
isStaged = {
message: `Main material: ${mainMateiral[0].MaterialHumanReadableId} - ${mainMateiral[0].MaterialDescription}: is not staged for ${lot.lot} `,
success: false,
};
}
// we need to filter the color stuff and then look for includes instead of a standard name. this way we can capture a everything and not a single type
// for manual consume color if active to check colors
const checkColorSetting = set.filter((n) => n.name === "checkColor");
if (checkColorSetting[0].value === "1") {
const autoConsumeColor = res.filter(
(n: any) => !n.IsMainMaterial && !n.isManual
);
if (
autoConsumeColor.some(
(n: any) => n.autoConsumeCheck === "autoConsumeNOK"
)
) {
const onlyNOK = autoConsumeColor.filter(
(n: any) => n.autoConsumeCheck === "autoConsumeNOK"
);
isStaged = {
message: `lot: ${lot.lot}, is missing: ${onlyNOK
.map(
(o: any) =>
`${o.MaterialHumanReadableId} - ${o.MaterialDescription}`
)
.join(",\n ")} for autoconsume`,
success: false,
};
}
// // for manual consume color
const manualConsumeColor = res.filter(
(n: any) => !n.IsMainMaterial && n.isManual
);
if (
manualConsumeColor.some(
(n: any) => n.noMaterialShortage === "yes"
)
) {
isStaged = {
message: `lot: ${lot.lot}, is missing: ${manualConsumeColor
.map(
(o: any) =>
`${o.MaterialHumanReadableId} - ${o.MaterialDescription}`
)
.join(",\n ")} for manual Material`,
success: false,
};
}
} else {
createLog(
"info",
"mainMaterial",
"ocp",
"Color check is not active."
);
}
// // if we want to check the packaging
const checkPKGSetting = set.filter((n) => n.name === "checkPKG");
if (checkPKGSetting[0].value === "1") {
const packagingCheck = res.filter(
(n: any) => !n.IsMainMaterial && n.isManual
);
if (packagingCheck.some((n: any) => n.noPKGShortage === "noPkg")) {
isStaged = {
message: `lot: ${lot.lot}, is missing: ${packagingCheck
.map(
(o: any) =>
`${o.MaterialHumanReadableId} - ${o.MaterialDescription}`
)
.join(",\n ")} for pkg`,
success: false,
};
}
} else {
createLog(
"info",
"mainMaterial",
"ocp",
"PKG check is not active."
);
}
} catch (err) { } catch (err) {
createLog( createLog(
"error", "error",

View File

@@ -0,0 +1,177 @@
import { Controller, Tag } from "st-ethernet-ip";
import { labelingProcess } from "../../labeling/labelProcess.js";
import { createLog } from "../../../../logger/logger.js";
let plcAddress = "192.168.193.97"; // zechetti 2
let lastProcessedTimestamp = 0;
let PLC = new Controller() as any;
const labelerTag = new Tag("N7[0]"); // change the car to a or b depending on what zechetti.
//const t = new Tag("CONV_M01_SHTL_UNLD_IN_FROM_PREV_CONV_TRACK_CODE.PAL_ORIGIN_LINE_N") // this is for the new zechette to reach the pallet form
let pollingInterval: any = null;
let heartbeatInterval: any = null;
let reconnecting = false;
let lastTag = 0;
// Track last successful read
let lastHeartbeat: number = Date.now();
export async function zechitti1Connect() {
try {
createLog(
"info",
"zechitti1",
"ocp",
`Connecting to PLC at ${plcAddress}...`
);
await PLC.connect(plcAddress, 0);
createLog("info", "zechitti1", "ocp", "Zechetti 2 connected.");
// Start polling tags
startPolling();
// Start heartbeat
// startHeartbeat();
// Handle disconnects/errors
PLC.on("close", () => {
console.warn("PLC connection closed.");
handleReconnect();
});
PLC.on("error", (err: any) => {
createLog("error", "zechitti1", "ocp", `PLC error: ${err.message}`);
handleReconnect();
});
} catch (err: any) {
createLog(
"error",
"zechitti1",
"ocp",
`Initial connection failed: ${err.message}`
);
handleReconnect();
}
}
function startPolling() {
if (pollingInterval) clearInterval(pollingInterval);
pollingInterval = setInterval(async () => {
try {
await PLC.readTag(labelerTag);
//lastHeartbeat = Date.now();
const tagTime: any = new Date(labelerTag.timestamp);
// so we make sure we are not missing a pallet remove it from the lastTag so we can get this next label correctly
if (
labelerTag.value == 0 &&
Date.now() - lastProcessedTimestamp >= 45000
) {
lastTag = labelerTag.value;
}
// if the tag is not zero and its been longer than 30 seconds and the last tag is not equal to the current tag we can print
if (
labelerTag.value !== 0 &&
lastTag !== labelerTag.value &&
tagTime !== lastProcessedTimestamp &&
Date.now() - lastProcessedTimestamp >= 30000
) {
lastProcessedTimestamp = tagTime;
lastTag = labelerTag.value;
console.log(
`Time since last check: ${
Date.now() - tagTime
}, greater than 30000, ${
Date.now() - lastProcessedTimestamp >= 30000
}, the line to be printed is ${labelerTag.value}`
);
//console.log(labelerTag);
const zechette = {
line: labelerTag.value.toString(),
printer: 22, // this is the id of the zechetti 2 to print we should move this to the db
printerName: "Zechetti1",
};
labelingProcess({ zechette: zechette });
}
} catch (err: any) {
createLog(
"error",
"zechitti1",
"ocp",
`Polling error: ${err.message}`
);
handleReconnect();
}
}, 1000);
}
// function startHeartbeat() {
// if (heartbeatInterval) clearInterval(heartbeatInterval);
// heartbeatInterval = setInterval(() => {
// const diff = Date.now() - lastHeartbeat;
// if (diff > 60000) {
// // 1 minute
// console.warn(`⚠️ Heartbeat timeout: no data for ${diff / 1000}s`);
// handleReconnect();
// }
// }, 10000); // check every 10s
// }
async function handleReconnect() {
if (reconnecting) return;
reconnecting = true;
if (pollingInterval) {
clearInterval(pollingInterval);
pollingInterval = null;
}
let delay = 2000; // start at 2s
let attempts = 0;
const maxAttempts = 10; // or limit by time, e.g. 2 min total
while (!PLC.connected && attempts < maxAttempts) {
attempts++;
createLog(
"info",
"zechitti1",
"ocp",
`Reconnect attempt ${attempts}/${maxAttempts} in ${
delay / 1000
}s...`
);
await new Promise((res) => setTimeout(res, delay));
try {
PLC = new Controller(); // fresh instance
await PLC.connect(plcAddress, 0);
createLog("info", "zechitti1", "ocp", "Reconnected to PLC!");
reconnecting = false;
startPolling();
return;
} catch (err: any) {
createLog(
"error",
"zechitti1",
"ocp",
`Reconnect attempt failed: ${err.message}`
);
delay = Math.min(delay * 2, 30000); // exponential backoff up to 30s
}
}
if (!PLC.connected) {
createLog(
"error",
"zechitti1",
"ocp",
"Max reconnect attempts reached. Stopping retries."
);
reconnecting = false;
// optional: exit process or alert someone here
// process.exit(1);
}
}

View File

@@ -24,6 +24,9 @@ import { deleteLabels } from "../../globalUtils/dbCleanUp/labelCleanUp.js";
import bookInLabel from "./routes/labeling/bookIn.js"; import bookInLabel from "./routes/labeling/bookIn.js";
import labelRatio from "./routes/labeling/getLabelRatio.js"; import labelRatio from "./routes/labeling/getLabelRatio.js";
import resetRatio from "./routes/labeling/resetLabelRatio.js"; import resetRatio from "./routes/labeling/resetLabelRatio.js";
import materialTransferLot from "./routes/materials/lotTransfer.js";
import pendingTransfers from "./routes/materials/currentPending.js";
import { zechitti1Connect } from "./controller/specialProcesses/zechettis/zechetti1.js";
const app = new OpenAPIHono(); const app = new OpenAPIHono();
@@ -47,6 +50,9 @@ const routes = [
//dyco //dyco
dycoCon, dycoCon,
dycoClose, dycoClose,
// materials
materialTransferLot,
pendingTransfers,
] as const; ] as const;
const setting = await db.select().from(settings); const setting = await db.select().from(settings);
@@ -66,6 +72,7 @@ app.all("/ocp/*", (c) => {
*/ */
const dycoActive = setting.filter((n) => n.name == "dycoConnect"); const dycoActive = setting.filter((n) => n.name == "dycoConnect");
const ocpActive = setting.filter((n) => n.name === "ocpActive"); const ocpActive = setting.filter((n) => n.name === "ocpActive");
const zechetti = setting.filter((n) => n.name == "zechetti");
// run the printer update on restart just to keep everything good // run the printer update on restart just to keep everything good
// do the intnal connection to the dyco // do the intnal connection to the dyco
@@ -75,6 +82,13 @@ setTimeout(() => {
} }
}, 3 * 1000); }, 3 * 1000);
// if zechetti plc is wanted we will connect
setTimeout(() => {
if (zechetti[0]?.value === "1") {
zechitti1Connect();
}
}, 3 * 1000);
// check for printers being assigned // check for printers being assigned
setInterval(() => { setInterval(() => {
if (ocpActive[0]?.value === "1") { if (ocpActive[0]?.value === "1") {
@@ -101,4 +115,5 @@ setInterval(() => {
setInterval(() => { setInterval(() => {
updatePrinters(); updatePrinters();
}, 1000 * 60 * 60 * 24); }, 1000 * 60 * 60 * 24);
export default app; export default app;

View File

@@ -0,0 +1,51 @@
// an external way to creating logs
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
import { responses } from "../../../../globalUtils/routeDefs/responses.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { apiHit } from "../../../../globalUtils/apiHits.js";
import { pendingJobs } from "../../controller/materials/lotTransfer.js";
import { format, formatDuration, intervalToDuration } from "date-fns";
const app = new OpenAPIHono({ strict: false });
app.openapi(
createRoute({
tags: ["ocp"],
summary: "Returns pending transfers",
method: "get",
path: "/pendingtransfers",
responses: responses(),
}),
async (c) => {
apiHit(c, { endpoint: "/pendingtransfers" });
const pending = Array.from(pendingJobs.entries()).map(
([runningNumber, job]) => {
const duration = intervalToDuration({
start: new Date(),
end: job.scheduledFor,
});
return {
runningNumber,
lot: job.data?.lotNumber,
newQty: job.newQty,
consumeLot: job.consumeLot,
scheduledFor: format(job.scheduledFor, "M/d/yyyy HH:mm"),
remainingMs: formatDuration(duration, {
format: ["hours", "minutes", "seconds"],
}),
};
}
);
//console.log(pending);
return c.json({
success: true,
message: "Current Pending trnasfers",
data: [pending],
});
}
);
export default app;

View File

@@ -0,0 +1,66 @@
// an external way to creating logs
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
import { responses } from "../../../../globalUtils/routeDefs/responses.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { apiHit } from "../../../../globalUtils/apiHits.js";
import { lotMaterialTransfer } from "../../controller/materials/lotTransfer.js";
const app = new OpenAPIHono({ strict: false });
const LotTransfer = z.object({
runningNumber: z.number().openapi({ example: 1234 }),
lotNumber: z.number().openapi({ example: 1235 }),
originalAmount: z.number().openapi({ example: 457 }),
level: z.number().openapi({ examples: [0.24, 0.5, 0.75, 0.95] }),
});
app.openapi(
createRoute({
tags: ["ocp"],
summary: "Transfers a gaylord of material to provided lot",
method: "post",
path: "/materiallottransfer",
request: {
body: { content: { "application/json": { schema: LotTransfer } } },
},
responses: responses(),
}),
async (c) => {
//const hours = c.req.query("hours");
const { data: bodyData, error: bodyError } = await tryCatch(
c.req.json()
);
apiHit(c, { endpoint: "/materiallottransfer", lastBody: bodyData });
if (bodyError) {
return c.json({
success: false,
message: "You are missing data",
});
}
const { data: transferMaterial, error: transferError } = await tryCatch(
lotMaterialTransfer(bodyData)
);
if (transferError) {
//console.log(transferError);
return c.json({
success: false,
message:
"There was an error transfering the material to the next lot.",
data: transferError,
});
}
console.log(transferMaterial);
return c.json({
success: transferMaterial?.success,
message: transferMaterial?.message,
data: transferMaterial?.data,
});
}
);
export default app;

View File

@@ -59,6 +59,8 @@ const newProdRoles: any = [
"Logistics\\Warehousing\\ProcessAdmin", "Logistics\\Warehousing\\ProcessAdmin",
"Manufacturing\\IssueMaterial\\ProcessAdmin", "Manufacturing\\IssueMaterial\\ProcessAdmin",
"Manufacturing\\ProductionLabelling\\ProcessAdmin", "Manufacturing\\ProductionLabelling\\ProcessAdmin",
"DemandManagement\\Forecast\\ProcessAdmin",
"DemandManagement\\Order\\ProcessAdmin",
], ],
rolesLegacy: [55, 95, 15, 105, 145, 9], rolesLegacy: [55, 95, 15, 105, 145, 9],
}, },

View File

@@ -7,6 +7,7 @@ import updateReader from "./route/updateReader.js";
import manualTrigger from "./route/manualTagRead.js"; import manualTrigger from "./route/manualTagRead.js";
import getReaders from "./route/getReaders.js"; import getReaders from "./route/getReaders.js";
import resetRatio from "./route/resetRatio.js"; import resetRatio from "./route/resetRatio.js";
import { monitorRfidTags } from "./utils/monitorTags.js";
const app = new OpenAPIHono(); const app = new OpenAPIHono();
const routes = [ const routes = [
@@ -24,4 +25,9 @@ const appRoutes = routes.forEach((route) => {
app.route("/rfid", route); app.route("/rfid", route);
}); });
// monitor every 5 min tags older than 6 hours to remove the line they were so we reduce the risk of them being labeled with the wrong info
setInterval(() => {
monitorRfidTags();
}, 5 * 1000 * 60);
export default app; export default app;

View File

@@ -0,0 +1,22 @@
import { and, lt, ne, sql } from "drizzle-orm";
import { db } from "../../../../database/dbclient.js";
import { rfidTags } from "../../../../database/schema/rfidTags.js";
import { tryCatch } from "../../../globalUtils/tryCatch.js";
/**
* This will monitor tags that are older than 6hours and are still linked to a line.
* it will then remove the line from the last area in as we will asume it dose not exist.
*/
export const monitorRfidTags = async () => {
const { data, error } = await tryCatch(
db
.update(rfidTags)
.set({ lastareaIn: "miss scanned" })
.where(
and(
ne(rfidTags.lastareaIn, "wrapper1"), // not equal to 'wrapper1'
lt(rfidTags.lastRead, sql`NOW() - INTERVAL '6 hours'`) // older than 6 hours)
)
)
);
};

View File

@@ -198,6 +198,13 @@ const newSettings = [
serviceBelowsTo: "system", serviceBelowsTo: "system",
roleToChange: "admin", roleToChange: "admin",
}, },
{
name: "rifd",
value: `0`,
description: "This is for dayton to be runnning rfid pallet tracking.",
serviceBelowsTo: "logistics",
roleToChange: "admin",
},
// ocp // ocp
{ {
@@ -244,7 +251,28 @@ const newSettings = [
serviceBelowsTo: "logistics", serviceBelowsTo: "logistics",
roleToChange: "admin", roleToChange: "admin",
}, },
{
name: "zechetti",
value: `0`,
description: "Are we going to be running the Zechetti plcs",
serviceBelowsTo: "logistics",
roleToChange: "admin",
},
// temp settings can be deleted at a later date once that code is removed // temp settings can be deleted at a later date once that code is removed
{
name: "checkColor",
value: `0`,
description: "Checks autoconsume and manual consume color",
serviceBelowsTo: "admin",
roleToChange: "admin",
},
{
name: "checkPKG",
value: `0`,
description: "Checks checks if we have enough packaging or not",
serviceBelowsTo: "admin",
roleToChange: "admin",
},
{ {
name: "siloAdjMigrations", name: "siloAdjMigrations",
value: `0`, value: `0`,

View File

@@ -61,7 +61,14 @@ const newSubModules = [
description: "", description: "",
link: "/materialHelper/consumption", link: "/materialHelper/consumption",
icon: "Package", icon: "Package",
roles: ["technician", "supervisor", "manager", "admin", "systemAdmin"], roles: [
"viewer",
"technician",
"supervisor",
"manager",
"admin",
"systemAdmin",
],
active: false, active: false,
subSubModule: [], subSubModule: [],
}, },

View File

@@ -0,0 +1,4 @@
export const shiftChange = `
select top(1) convert(varchar(8) ,convert(time,startdate), 108) as shiftChange
from [test1_AlplaPROD2.0_Read].[masterData].[ShiftDefinition] where teamNumber = 1
`;

View File

@@ -1,22 +1,117 @@
// export const mmQuery = `
// SELECT lot.ProductionLotHumanReadableId,
// case when SourcingState in (1, 2) then 1
// when x.ProvidedAmount > 0 then 1
// when x.EffectiveConsumption > 0 then 1
// else 0 end as Staged,
// x.ProvidedAmount as Provided,
// x.EffectiveConsumption as consumption,
// x.TotalDemand as totalNeeded,
// /* remaining needed to complete the lot */
// x.TotalDemand - x.EffectiveConsumption as remainingNeeded,
// /* do we have enough staged or scanned to the lot? */
// case when (case when x.ProvidedAmount <> 0 then x.ProvidedAmount else x.EffectiveConsumption end) > x.TotalDemand then 'good' else 'bad' end as noShortage ,
// x.IsManualProcess as isManual,
// MaterialHumanReadableId
// FROM [test1_AlplaPROD2.0_Read].[issueMaterial].[MaterialDemand] x (nolock)
// left join
// [test1_AlplaPROD2.0_Read].[issueMaterial].[ProductionLot] as lot on
// x.ProductionLotId = lot.Id
// where lot.ProductionLotHumanReadableId = [lotNumber]
// and IsMainMaterial = 1
// `;
export const mmQuery = ` export const mmQuery = `
SELECT lot.ProductionLotHumanReadableId, use [test1_AlplaPROD2.0_Read]
case when SourcingState in (1, 2) then 1 /*
when x.ProvidedAmount > 0 then 1 checks all needed material including pkg
when x.EffectiveConsumption > 0 then 1
else 0 end as Staged, we only want to monitor the manual materials and the mm materail to make sure they are good.
x.ProvidedAmount as Provided,
x.EffectiveConsumption as consumption, for auto consume materails this will be compared with another query.
x.TotalDemand as totalNeeded,
*/
SELECT lot.ProductionLotHumanReadableId
,MaterialHumanReadableId
,MaterialDescription
--IsMainMaterial,
,case when SourcingState in (1, 2) then 1
when x.ProvidedAmount > 0 then 1
when x.EffectiveConsumption > 0 then 1
else 0 end as Staged
,x.ProvidedAmount as Provided
,x.EffectiveConsumption as consumption
,x.TotalDemand as totalDemand ,
case when cp.Pieces = 1 then (lot.TotalProducedLoadingUnits+1) * p.LoadingUnitPieces else
(a.Weight *((case when cp.Percentage is null then 80.25 else cp.Percentage end) / 100) * ((lot.TotalProducedLoadingUnits+1) * p.LoadingUnitPieces)) / 1000 end totalNeeded
,l.qty as invForAutoConsume
/* remaining needed to complete the lot */ /* remaining needed to complete the lot */
x.TotalDemand - x.EffectiveConsumption as remainingNeeded, --,x.TotalDemand - x.EffectiveConsumption as remainingNeeded
/* do we have enough staged or scanned to the lot? */ /* do we have enough staged or scanned to the lot? */
case when (case when x.ProvidedAmount <> 0 then x.ProvidedAmount else x.EffectiveConsumption end) > x.TotalDemand then 'good' else 'bad' end as noShortage , ,case when (case when x.ProvidedAmount <> 0
x.IsManualProcess as isManual, then x.ProvidedAmount else x.EffectiveConsumption end) >
MaterialHumanReadableId (case when cp.Pieces = 1 then (lot.TotalProducedLoadingUnits+1) * p.LoadingUnitPieces else
FROM [test1_AlplaPROD2.0_Read].[issueMaterial].[MaterialDemand] x (nolock) (a.Weight *((case when cp.Percentage is null then 80.25 else cp.Percentage end) / 100) * ((lot.TotalProducedLoadingUnits+1) * p.LoadingUnitPieces)) / 1000 end)
then 'good'
else 'no' end as noMaterialShortage
-- pkg check
,case when pkg.QuantityPosition is null then null else
(case when l.qty > (lot.TotalProducedLoadingUnits+1) * pkg.QuantityPosition then 'pkgGood' else 'noPkg' end) end as noPKGShortage
,case when cp.Percentage is null then
case when l.qty > ((lot.TotalProducedLoadingUnits +1) * pkg.QuantityPosition) then 'autoConsumeOk' else 'autoConsumeNOK' end else
(case when l.qty > (a.Weight *((case when cp.Percentage is null then 80.25 else cp.Percentage end) / 100) * ((lot.TotalProducedLoadingUnits+1) * p.LoadingUnitPieces)) / 1000 and IsManualProcess = 0
then 'autoConsumeOk' else 'autoConsumeNOK' end) end as autoConsumeCheck
,x.IsManualProcess as isManual
,IsMainMaterial
,lot.TotalPlannedLoadingUnits
,lot.TotalProducedLoadingUnits
,lot.TotalPlannedLoadingUnits - lot.TotalProducedLoadingUnits as remainingPallets
,case when cp.Percentage is null then 80.25 else cp.Percentage end as Percentage -- this is to over come the alternate mm put in planning
,case when cp.Pieces is not null then cp.Pieces else pkg.QuantityPosition end as peices
FROM [issueMaterial].[MaterialDemand] x (nolock)
left join left join
[test1_AlplaPROD2.0_Read].[issueMaterial].[ProductionLot] as lot on [productionControlling].[ProducedLot] as lot on
x.ProductionLotId = lot.Id lot.Id = x.ProductionLotId
/* av data */
left join
[masterData].[Article] as a on
a.id = lot.ArticleId
/* compound*/
left join
[masterData].[CompoundPosition] as cp on
cp.CompoundId = a.CompoundId
and cp.ArticleId = x.MaterialId
/* packagaing */
left join
[masterData].[PackagingInstructionPosition] as pkg on
pkg.PackagingInstructionId = lot.PackagingId
and pkg.ArticleId = x.MaterialId
-- get the pkg stuff so we have the total amount per pallet.
left join
[masterData].[PackagingInstruction] as p on
p.id = lot.PackagingId
/* current stock info for auto consumption*/
left join
(select
IdArtikelVarianten
,ArtikelVariantenBez
,sum(VerfuegbareMengeSum) as qty
from AlplaPROD_test1.dbo.V_LagerPositionenBarcodes as i (nolock)
left join
AlplaPROD_test1.dbo.V_LagerAbteilungen as l (nolock) on
l.IdLagerAbteilung = i.IdLagerAbteilung
where autoverbrauch = 1 and aktiv = 1
group by IdArtikelVarianten,ArtikelVariantenBez
) as l on
l.IdArtikelVarianten = MaterialHumanReadableId
where lot.ProductionLotHumanReadableId = [lotNumber] where lot.ProductionLotHumanReadableId = [lotNumber]
and IsMainMaterial = 1
`; `;

View File

@@ -1,20 +1,36 @@
export const labelInfo = ` export const labelInfo = `
select top(500) e.Barcode, declare @runningNumber nvarchar(max) = [runningNr]
e.IdArtikelvarianten as av,
e.lfdnr as rn, select e.Barcode,
IdEtikettenLayout as labelLayout, e.RunningNumber as runnungNumber,
AnzStkJePalette, externalRunningNumber= null,
Sonstiges_9,AnzStkJeKarton e.ArticleHumanReadableId as av,
,case when EinlagerungsMengeSum IS NULL then 'notOnStock' else 'onStock' end as stockStatus e.LabelManagementHumanReadableId as labelLayout,
,EinlagerungsMengeSum case when EinlagerungsMengeSum IS NULL then 'notOnStock' else 'onStock' end as stockStatus
--,* from [test1_AlplaPROD2.0_Read].[labelling].[InternalLabel] (nolock) as e
from [AlplaPROD_test1].dbo.[T_EtikettenGedruckt] e (nolock)
left join left join
[AlplaPROD_test1].dbo.V_LagerPositionenBarcodes as l on alplaprod_test1.dbo.V_LagerPositionenBarcodes (nolock) as l on
e.LfdNr = l.Lfdnr e.RunningNumber = l.Lfdnr
where e.LfdNr in ([runningNr]) WHERE e.RunningNumber IN (@runningNumber)
order by add_date desc union all
select ext.Barcode
,RunningNumber as runnungNumber
,SsccEanRunningNumber as externalRunningNumber
,ArticleHumanReadableId
,case when LabelManagementHumanReadableId is null then (select HumanReadableId from [test1_AlplaPROD2.0_Read].[masterData].[LabelManagement] (nolock) where LabelMarkerId = 7) else LabelManagementHumanReadableId end as labelLayout
,case when EinlagerungsMengeSum IS NULL then 'notOnStock' else 'onStock' end as stockStatus
from [test1_AlplaPROD2.0_Read].[labelling].[ExternalLabel] (nolock) as ext
left join
alplaprod_test1.dbo.V_LagerPositionenBarcodes (nolock) as l on
ext.RunningNumber = l.Lfdnr
WHERE ext.SsccEanRunningNumber IN (@runningNumber) and
ext.RunningNumber NOT IN (
SELECT RunningNumber FROM [test1_AlplaPROD2.0_Read].[labelling].[InternalLabel] WHERE RunningNumber IN (@runningNumber)
)
`; `;