Compare commits
48 Commits
2ff7b9baf9
...
468f933168
| Author | SHA1 | Date | |
|---|---|---|---|
| 468f933168 | |||
| 8c296c5b78 | |||
| 53ed2c4e6a | |||
| 0a6ddea8c0 | |||
| df423192bf | |||
| 2f908398bc | |||
| bf8203bbee | |||
| e92eccf785 | |||
| f4f3de49ca | |||
| 788d6367a3 | |||
| 24a97afe06 | |||
| 37f82a9710 | |||
| 369d16018c | |||
| 68901a857a | |||
| 171763184c | |||
| b7de2a8dbe | |||
| e78083f496 | |||
| 3f3b64bf2b | |||
| 123bb127e8 | |||
| 8d6ead3aa1 | |||
| 3148aa79d7 | |||
| 4486fe2436 | |||
| 0ba338d480 | |||
| 846ac479b1 | |||
| 73d38ba3fe | |||
| 27586e923a | |||
| 662a951b98 | |||
| 0c54cecbd4 | |||
| bcf5378966 | |||
| c5668e6cf1 | |||
| 213814b868 | |||
| 7d5b0c46c1 | |||
| 595e22e8e9 | |||
| a6f18554b8 | |||
| 88f61c8eaa | |||
| a183279268 | |||
| 5156c8bf7b | |||
| 4669ff95dc | |||
| ed93992165 | |||
| f16e2bf53b | |||
| a84998438c | |||
| 83469105f0 | |||
| 835ae58f04 | |||
| 1498a19121 | |||
| ca96849991 | |||
| 27b37f9849 | |||
| 7f81a2e09a | |||
| e14abd3644 |
31
CHANGELOG.md
31
CHANGELOG.md
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
0
database/schema/forkliftHours.ts
Normal file
0
database/schema/forkliftHours.ts
Normal file
0
database/schema/forkliftLeases.ts
Normal file
0
database/schema/forkliftLeases.ts
Normal file
0
database/schema/forklifts.ts
Normal file
0
database/schema/forklifts.ts
Normal file
48
frontend/package-lock.json
generated
48
frontend/package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export default function MaterialCheck() {
|
||||||
|
return <div>MaterialCheck</div>;
|
||||||
|
}
|
||||||
211
frontend/src/components/eom/materialsData/MaterialData.tsx
Normal file
211
frontend/src/components/eom/materialsData/MaterialData.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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",
|
||||||
|
|||||||
29
frontend/src/components/ui/switch.tsx
Normal file
29
frontend/src/components/ui/switch.tsx
Normal 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 }
|
||||||
@@ -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
164
package-lock.json
generated
@@ -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"
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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"))
|
||||||
|
|||||||
@@ -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/>
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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.");
|
||||||
|
|
||||||
|
|||||||
426
server/services/ocp/controller/materials/lotTransfer.ts
Normal file
426
server/services/ocp/controller/materials/lotTransfer.ts
Normal 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);
|
||||||
@@ -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",
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
51
server/services/ocp/routes/materials/currentPending.ts
Normal file
51
server/services/ocp/routes/materials/currentPending.ts
Normal 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;
|
||||||
66
server/services/ocp/routes/materials/lotTransfer.ts
Normal file
66
server/services/ocp/routes/materials/lotTransfer.ts
Normal 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;
|
||||||
@@ -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],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
22
server/services/rfid/utils/monitorTags.ts
Normal file
22
server/services/rfid/utils/monitorTags.ts
Normal 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)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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`,
|
||||||
|
|||||||
@@ -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: [],
|
||||||
},
|
},
|
||||||
|
|||||||
4
server/services/sqlServer/querys/misc/shiftChange.ts
Normal file
4
server/services/sqlServer/querys/misc/shiftChange.ts
Normal 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
|
||||||
|
`;
|
||||||
@@ -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
|
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -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)
|
||||||
|
)
|
||||||
`;
|
`;
|
||||||
|
|||||||
Reference in New Issue
Block a user