@seedtactics/insight-client 17.0.1-beta.13 → 17.0.1-beta.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +5 -4
  2. package/dist/assets/index-Z6GAS_qc.js +367 -0
  3. package/dist/cell-status/loading.d.ts +7 -0
  4. package/dist/cell-status/loading.js +25 -0
  5. package/dist/components/App.d.ts +0 -5
  6. package/dist/components/App.js +1 -1
  7. package/dist/components/operations/AllMaterial.js +9 -12
  8. package/dist/components/station-monitor/BasketLoadStationWork.d.ts +1 -7
  9. package/dist/components/station-monitor/BasketLoadStationWork.js +11 -7
  10. package/dist/components/station-monitor/InvalidateCycle.js +21 -17
  11. package/dist/components/station-monitor/LoadStation.d.ts +0 -5
  12. package/dist/components/station-monitor/LoadStation.js +29 -69
  13. package/dist/components/station-monitor/Material.js +4 -2
  14. package/dist/components/station-monitor/QuarantineButton.js +17 -26
  15. package/dist/components/station-monitor/Whiteboard.js +14 -15
  16. package/dist/data/material-operation-policy.d.ts +6 -0
  17. package/dist/data/material-operation-policy.js +26 -2
  18. package/dist/extension.d.ts +0 -1
  19. package/dist/index.html +1 -1
  20. package/dist/index.js +1 -6
  21. package/dist/network/api.d.ts +16 -34
  22. package/dist/network/api.js +64 -64
  23. package/dist/network/backend-mock.js +3 -0
  24. package/dist/network/backend.d.ts +1 -0
  25. package/docs/material-quarantine.md +5 -4
  26. package/docs/mazak.md +9 -12
  27. package/docs/operator-procedures.md +14 -0
  28. package/package.json +9 -4
  29. package/dist/assets/index-Dxxgr-QV.js +0 -367
  30. package/dist/components/station-monitor/BasketMovementArrival.d.ts +0 -40
  31. package/dist/components/station-monitor/BasketMovementArrival.js +0 -165
@@ -1,4 +1,5 @@
1
1
  import { ICurrentStatus, IHistoricData, ILogEntry, IRecentHistoricData, IServerEvent } from "../network/api.js";
2
+ import * as currentSt from "./current-status.js";
2
3
  import { Atom } from "jotai";
3
4
  export interface ServerEventAndTime {
4
5
  readonly evt: Readonly<IServerEvent>;
@@ -24,6 +25,12 @@ export declare const onLoadLast30Log: import("jotai").WritableAtom<null, [log: r
24
25
  export declare const onLoadCurrentSt: import("jotai").WritableAtom<null, [curSt: Readonly<ICurrentStatus>], void> & {
25
26
  init: null;
26
27
  };
28
+ export interface QueuedMatMove extends currentSt.QueueReordering {
29
+ readonly operator: string | null;
30
+ }
31
+ export declare const moveQueuedMatInCurrentStatus: import("jotai").WritableAtom<null, [QueuedMatMove], Promise<void>> & {
32
+ init: null;
33
+ };
27
34
  export declare const onLoadSpecificMonthJobs: import("jotai").WritableAtom<null, [historicData: Readonly<IHistoricData>], void> & {
28
35
  init: null;
29
36
  };
@@ -30,6 +30,8 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30
30
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31
31
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
32
  */
33
+ import { QueuePosition, } from "../network/api.js";
34
+ import { JobsBackend } from "../network/backend.js";
33
35
  import { LazySeq } from "@seedtactics/immutable-collections";
34
36
  import * as simProd from "./sim-production.js";
35
37
  import * as simUse from "./sim-station-use.js";
@@ -112,6 +114,29 @@ export const onLoadCurrentSt = atom(null, (_, set, curSt) => {
112
114
  set(customSt.replaceCustomState, curSt.customState ?? null);
113
115
  set(names.setNamesFromCurrentStatus, curSt);
114
116
  });
117
+ // Show queue drops immediately. Recovery must not replace intervening live updates or drags.
118
+ export const moveQueuedMatInCurrentStatus = atom(null, async (get, set, { operator, ...move }) => {
119
+ const before = get(currentSt.currentStatus);
120
+ set(currentSt.reorderQueuedMatInCurrentStatus, move);
121
+ const optimistic = get(currentSt.currentStatus);
122
+ try {
123
+ await JobsBackend.setMaterialInQueue(move.matId, operator, new QueuePosition({ queue: move.queue, position: move.newIdx }));
124
+ }
125
+ catch (error) {
126
+ const beforeReload = get(currentSt.currentStatus);
127
+ try {
128
+ const refreshed = await JobsBackend.currentStatus();
129
+ if (get(currentSt.currentStatus) === beforeReload)
130
+ set(onLoadCurrentSt, refreshed);
131
+ }
132
+ catch {
133
+ // If the server is unreachable, undo only this move's untouched optimistic snapshot.
134
+ if (get(currentSt.currentStatus) === optimistic)
135
+ set(onLoadCurrentSt, before);
136
+ }
137
+ throw error;
138
+ }
139
+ });
115
140
  export const onLoadSpecificMonthJobs = atom(null, (_, set, historicData) => {
116
141
  set(simUse.setSpecificMonthSimStatUse, historicData);
117
142
  set(simProd.setSpecificMonthJobProduction, historicData);
@@ -1,8 +1,6 @@
1
1
  import { ReactNode, ComponentType } from "react";
2
2
  import { ChooseModeItem } from "./ChooseMode.js";
3
3
  import * as serverSettings from "../network/server-settings.js";
4
- import type { SubmitBasketLoadStationCommand } from "./station-monitor/BasketLoadStationWork.js";
5
- import type { SubmitBasketLocationCorrection, SubmitBasketMovementCompletion } from "./station-monitor/BasketMovementArrival.js";
6
4
  export declare function NavTabs({ children }: {
7
5
  children?: ReactNode;
8
6
  }): import("react").JSX.Element;
@@ -12,9 +10,6 @@ export interface AppProps {
12
10
  readonly page: ReactNode;
13
11
  };
14
12
  readonly chooseModes?: (i: serverSettings.FMSInfoAndUser) => ReadonlyArray<ChooseModeItem> | null;
15
- readonly submitBasketLoadStationCommand?: SubmitBasketLoadStationCommand;
16
- readonly submitBasketMovementCompletion?: SubmitBasketMovementCompletion;
17
- readonly submitBasketLocationCorrection?: SubmitBasketLocationCorrection;
18
13
  }
19
14
  declare const App: import("react").MemoExoticComponent<(props: AppProps) => import("react").JSX.Element>;
20
15
  export default App;
@@ -310,7 +310,7 @@ const App = memo(function App(props) {
310
310
  else {
311
311
  switch (route.route) {
312
312
  case routes.RouteLocation.Station_LoadMonitor:
313
- page = (_jsx(LoadStation, { loadNum: route.loadNum, queues: route.queues, completed: route.completed, submitBasketLoadStationCommand: props.submitBasketLoadStationCommand, submitBasketMovementCompletion: props.submitBasketMovementCompletion, submitBasketLocationCorrection: props.submitBasketLocationCorrection }, route.loadNum));
313
+ page = (_jsx(LoadStation, { loadNum: route.loadNum, queues: route.queues, completed: route.completed }, route.loadNum));
314
314
  nav1 = StationToolbar;
315
315
  nav2 = StationToolbarOverviewButton;
316
316
  showOperator = true;
@@ -33,8 +33,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
33
  */
34
34
  import { forwardRef, memo, PureComponent, useState, useCallback, useMemo, } from "react";
35
35
  import { selectAllMaterialIntoBins, MaterialBinType, moveMaterialBin, currentMaterialBinOrder, moveMaterialInBin, findMaterialInQuarantineQueues, findQueueInQuarantineQueues, } from "../../data/all-material-bins.js";
36
- import * as matDetails from "../../cell-status/material-details.js";
37
36
  import * as currentSt from "../../cell-status/current-status.js";
37
+ import { moveQueuedMatInCurrentStatus } from "../../cell-status/loading.js";
38
38
  import { Box } from "@mui/material";
39
39
  import { Typography } from "@mui/material";
40
40
  import { LazySeq } from "@seedtactics/immutable-collections";
@@ -48,6 +48,7 @@ import { useSetTitle } from "../routes.js";
48
48
  import { useAtom, useAtomValue, useSetAtom } from "jotai";
49
49
  import { fmsInformation } from "../../network/server-settings.js";
50
50
  import { basketDisplayName, loadStationDisplayName } from "../../cell-status/station-cycles.js";
51
+ import { canSetMaterialInQueue } from "../../data/material-operation-policy.js";
51
52
  const ColumnWithTitle = forwardRef(function MaterialBin(props, ref) {
52
53
  return (_jsxs(Box, { sx: {
53
54
  margin: props.isDragOverlay ? undefined : "0.75em",
@@ -187,8 +188,7 @@ export function AllMaterial(props) {
187
188
  useSetTitle("All Material");
188
189
  const st = useAtomValue(currentSt.currentStatus);
189
190
  const [matBinOrder, setMatBinOrder] = useAtom(currentMaterialBinOrder);
190
- const [addExistingMatToQueue] = matDetails.useAddExistingMaterialToQueue();
191
- const reorderQueuedMat = useSetAtom(currentSt.reorderQueuedMatInCurrentStatus);
191
+ const moveQueuedMat = useSetAtom(moveQueuedMatInCurrentStatus);
192
192
  const [activeDrag, setActiveDrag] = useState(null);
193
193
  const allBins = useMemo(() => {
194
194
  const bins = selectAllMaterialIntoBins(st, matBinOrder);
@@ -232,6 +232,8 @@ export function AllMaterial(props) {
232
232
  : findMaterialInQuarantineQueues(over.id, allBins);
233
233
  if (!overCol)
234
234
  return;
235
+ if (!canSetMaterialInQueue(activeDrag.mat, overCol.bin.queueName))
236
+ return;
235
237
  if (overCol.bin.binId !== activeDrag.curOverBinId) {
236
238
  setActiveDrag({
237
239
  ...activeDrag,
@@ -258,18 +260,13 @@ export function AllMaterial(props) {
258
260
  const overCol = typeof over.id === "string"
259
261
  ? findQueueInQuarantineQueues(over.id, allBins)
260
262
  : findMaterialInQuarantineQueues(over.id, allBins);
261
- if (overCol) {
262
- void addExistingMatToQueue({
263
- materialId: activeDrag.mat.materialID,
264
- queue: overCol.bin.queueName,
265
- queuePosition: overCol.idx,
266
- operator: null,
267
- }).catch(console.error);
268
- reorderQueuedMat({
263
+ if (overCol && canSetMaterialInQueue(activeDrag.mat, overCol.bin.queueName)) {
264
+ moveQueuedMat({
269
265
  queue: overCol.bin.queueName,
270
266
  matId: activeDrag.mat.materialID,
271
267
  newIdx: overCol.idx,
272
- });
268
+ operator: null,
269
+ }).catch(console.error);
273
270
  }
274
271
  }
275
272
  setActiveDrag(null);
@@ -1,15 +1,9 @@
1
1
  import * as api from "../../network/api.js";
2
2
  import { MatCardFontSize } from "./Material.js";
3
- export interface BasketLoadStationCommand {
4
- readonly workId: string;
5
- }
6
- export type SubmitBasketLoadStationCommand = (stationNumber: number, command: BasketLoadStationCommand) => Promise<"accepted" | "conflict">;
7
3
  interface BasketLoadStationWorkflowProps {
8
- readonly stationNumber: number;
9
4
  readonly basket: Readonly<api.IBasketStatus>;
10
5
  readonly material: ReadonlyArray<Readonly<api.IInProcessMaterial>>;
11
6
  readonly fsize: MatCardFontSize;
12
- readonly submitCommand: SubmitBasketLoadStationCommand | undefined;
13
7
  }
14
- export declare function BasketLoadStationWorkflow({ stationNumber, basket, material, fsize, submitCommand, }: BasketLoadStationWorkflowProps): import("react").JSX.Element;
8
+ export declare function BasketLoadStationWorkflow({ basket, material, fsize, }: BasketLoadStationWorkflowProps): import("react").JSX.Element;
15
9
  export {};
@@ -33,6 +33,7 @@ import { Alert, Box, Button, Stack, Typography } from "@mui/material";
33
33
  import { LazySeq } from "@seedtactics/immutable-collections";
34
34
  import { basketSlotLabel } from "../../cell-status/station-cycles.js";
35
35
  import * as api from "../../network/api.js";
36
+ import { JobsBackend } from "../../network/backend.js";
36
37
  import { InProcMaterial } from "./Material.js";
37
38
  import { MoveMaterialArrowNode } from "./MoveMaterialArrows.js";
38
39
  import { MoveMaterialNodeKindType } from "../../data/move-arrows.js";
@@ -138,10 +139,11 @@ function loadingInstruction(mat) {
138
139
  function SlotMaterial({ material, fsize, }) {
139
140
  return material.map((mat) => (_jsx(MoveMaterialArrowNode, { kind: { type: MoveMaterialNodeKindType.Material, material: mat }, children: _jsx(InProcMaterial, { mat: mat, fsize: fsize }) }, mat.materialID)));
140
141
  }
141
- export function BasketLoadStationWorkflow({ stationNumber, basket, material, fsize, submitCommand, }) {
142
+ export function BasketLoadStationWorkflow({ basket, material, fsize, }) {
142
143
  const [submission, setSubmission] = useState();
143
144
  const workState = useMemo(() => stationWork(material, basket), [basket, material]);
144
145
  const blockedReason = basket.loadStationWork?.confirmationBlockedReason?.trim();
146
+ const instructionWarning = basket.loadStationWork?.instructionWarning?.trim();
145
147
  const work = workState.type === "active" ? workState.work : undefined;
146
148
  const submissionState = submission !== undefined &&
147
149
  submission.workId === work?.workId &&
@@ -170,17 +172,19 @@ export function BasketLoadStationWorkflow({ stationNumber, basket, material, fsi
170
172
  .distinctBy((slot) => slot)
171
173
  .toSortedArray((slot) => slot), [basket.emptySlots, basket.unknownSlots, materialBySlot, loadsBySlot]);
172
174
  async function submit() {
173
- if (work === undefined || !work.ready || submitCommand === undefined)
175
+ if (work === undefined || !work.ready)
174
176
  return;
175
177
  const submittedWorkId = work.workId;
176
178
  const pending = { workId: submittedWorkId, basket, material, state: "submitting" };
177
179
  setSubmission(pending);
178
180
  try {
179
- const state = await submitCommand(stationNumber, { workId: submittedWorkId });
181
+ await JobsBackend.completeBasketLoadStation(new api.BasketLoadStationCompletion({ workId: submittedWorkId }));
182
+ const state = "accepted";
180
183
  setSubmission((current) => (current === pending ? { ...pending, state } : current));
181
184
  }
182
- catch {
183
- setSubmission((current) => (current === pending ? { ...pending, state: "error" } : current));
185
+ catch (error) {
186
+ const state = api.ApiException.isApiException(error) && error.status === 409 ? "conflict" : "error";
187
+ setSubmission((current) => (current === pending ? { ...pending, state } : current));
184
188
  }
185
189
  }
186
190
  const submissionDisabled = !work?.ready ||
@@ -202,8 +206,8 @@ export function BasketLoadStationWorkflow({ stationNumber, basket, material, fsi
202
206
  }, children: _jsxs(Box, { component: "section", "data-testid": `basket-load-station-slot-${slot}`, sx: { border: "1px solid", borderColor: "text.primary", p: 2 }, children: [_jsxs(Typography, { variant: "h6", children: ["Slot ", basketSlotLabel(slot)] }), loads.length > 0 &&
203
207
  slotMaterial.some((mat) => mat.action.type === api.ActionType.UnloadToInProcess ||
204
208
  mat.action.type === api.ActionType.UnloadToCompletedMaterial) && (_jsx(Typography, { children: "Unload the current material before preparing the replacement." })), slotMaterial.length > 0 ? (_jsx(SlotMaterial, { material: slotMaterial, fsize: fsize })) : (_jsx(Typography, { color: isUnknown ? "warning.main" : "text.secondary", children: isUnknown ? "Unknown" : "Empty" })), loads.map((mat, index) => (_jsxs(Box, { sx: { mt: 1 }, children: [_jsx(Typography, { children: loadingInstruction(mat) }), _jsxs(Typography, { variant: "body2", color: "text.secondary", children: [mat.partName, " \u00B7 Process ", mat.action.processAfterLoad ?? mat.process] })] }, `${mat.jobUnique}-${mat.process}-${index}`)))] }) }, slot));
205
- }) }), work?.confirmEmpty && _jsxs(Typography, { children: ["Confirm basket ", basket.basketId, " is empty."] }), work && !work.ready && (_jsx(Alert, { severity: "info", children: blockedReason ||
209
+ }) }), work?.confirmEmpty && _jsxs(Typography, { children: ["Confirm basket ", basket.basketId, " is empty."] }), instructionWarning && _jsx(Alert, { severity: "warning", children: instructionWarning }), work && !work.ready && (_jsx(Alert, { severity: "info", children: blockedReason ||
206
210
  (work.awaitingSlots.length > 0
207
211
  ? `Waiting for material for slots ${work.awaitingSlots.map(basketSlotLabel).join(", ")}.`
208
- : "Basket work is not ready for confirmation.") })), work && submitCommand ? (_jsx(Box, { sx: { display: "flex", justifyContent: "flex-end" }, children: _jsx(Button, { variant: "contained", disabled: submissionDisabled, onClick: () => void submit(), children: "Confirm" }) })) : work ? (_jsx(Alert, { severity: "warning", children: "Basket work confirmation is not configured." })) : workState.type === "inconsistent" ? (_jsx(Alert, { severity: "warning", children: "Basket work is inconsistent. Wait for refreshed material actions before confirming." })) : null, submissionState === "accepted" ? (_jsx(Alert, { severity: "success", children: "Confirmation accepted. Waiting for refreshed work." })) : submissionState === "conflict" ? (_jsx(Alert, { severity: "warning", children: "Basket work changed before confirmation. Review the refreshed slots and try again." })) : submissionState === "error" ? (_jsx(Alert, { severity: "error", children: "Unable to confirm basket work. No work was assumed complete." })) : null] }));
212
+ : "Basket work is not ready for confirmation.") })), work ? (_jsx(Box, { sx: { display: "flex", justifyContent: "flex-end" }, children: _jsx(Button, { variant: "contained", disabled: submissionDisabled, onClick: () => void submit(), children: "Confirm" }) })) : workState.type === "inconsistent" ? (_jsx(Alert, { severity: "warning", children: "Basket work is inconsistent. Wait for refreshed material actions before confirming." })) : null, submissionState === "accepted" ? (_jsx(Alert, { severity: "success", children: "Confirmation accepted. Waiting for refreshed work." })) : submissionState === "conflict" ? (_jsx(Alert, { severity: "warning", children: "Basket work changed before confirmation. Review the refreshed slots and try again." })) : submissionState === "error" ? (_jsx(Alert, { severity: "error", children: "Unable to confirm basket work. No work was assumed complete." })) : null] }));
209
213
  }
@@ -82,12 +82,15 @@ function InvalidateSelect(props) {
82
82
  job: currentSt.jobs[j.jobUnique] ?? history.get(j.jobUnique) ?? null,
83
83
  }));
84
84
  const hasProc = lastMat !== null && lastMat.process >= 1;
85
- const hasChangeMat = castings.length > 0 || jobs.length > 0;
85
+ const canChangeAssignment = lastMat === null || lastMat.process <= 1;
86
+ const hasChangeMat = canChangeAssignment && (castings.length > 0 || jobs.length > 0);
86
87
  const selectedCasting = props.st?.changeRawMat !== null &&
87
88
  props.st?.changeRawMat !== undefined &&
89
+ canChangeAssignment &&
88
90
  castings.includes(props.st.changeRawMat);
89
91
  const selectedJob = props.st?.changeJobUnique !== null &&
90
92
  props.st?.changeJobUnique !== undefined &&
93
+ canChangeAssignment &&
91
94
  jobs.some((job) => job.jobUnique === props.st?.changeJobUnique);
92
95
  function change(e) {
93
96
  props.setState({
@@ -102,7 +105,7 @@ function InvalidateSelect(props) {
102
105
  ? "rawMat" + props.st.changeRawMat
103
106
  : selectedJob
104
107
  ? "job" + props.st.changeJobUnique
105
- : props.st?.process && hasProc
108
+ : props.st?.process && hasProc && props.st.process === lastMat.process
106
109
  ? "proc" + props.st.process.toString()
107
110
  : "", select: true, onChange: (e) => change(e.target.value), variant: "outlined", label: props.st?.changeRawMat
108
111
  ? "Change Raw Material"
@@ -119,11 +122,6 @@ function InvalidateSelect(props) {
119
122
  hasProc
120
123
  ? [
121
124
  _jsxs(MenuItem, { value: "proc" + lastMat.process.toString(), children: ["Invalidate Process ", lastMat.process] }, lastMat.process),
122
- ...(lastMat.process > 1
123
- ? [
124
- _jsx(MenuItem, { value: "proc1", children: "Invalidate All Processes" }, "all"),
125
- ]
126
- : []),
127
125
  ]
128
126
  : [],
129
127
  hasProc && hasChangeMat
@@ -131,8 +129,9 @@ function InvalidateSelect(props) {
131
129
  : [],
132
130
  hasChangeMat
133
131
  ? [
134
- ...castings.map((c) => (_jsxs(MenuItem, { value: "rawMat" + c, children: ["Invalidate Everything and Change to ", c] }, "rawMat" + c))),
135
- ...jobs.map(({ jobUnique, job }) => (_jsxs(MenuItem, { value: "job" + jobUnique, children: [job ? (_jsx(ListItemIcon, { children: _jsx(PartIdenticon, { part: job.partName }) })) : undefined, _jsx(ListItemText, { primary: "Invalidate Everything and Change to Job " + jobUnique, secondary: job?.partName })] }, "job" + jobUnique))),
132
+ ...castings.map((c) => (_jsxs(MenuItem, { value: "rawMat" + c, children: [hasProc ? "Invalidate Process 1 and Change to " : "Change to ", c] }, "rawMat" + c))),
133
+ ...jobs.map(({ jobUnique, job }) => (_jsxs(MenuItem, { value: "job" + jobUnique, children: [job ? (_jsx(ListItemIcon, { children: _jsx(PartIdenticon, { part: job.partName }) })) : undefined, _jsx(ListItemText, { primary: (hasProc ? "Invalidate Process 1 and Change to Job " : "Change to Job ") +
134
+ jobUnique, secondary: job?.partName })] }, "job" + jobUnique))),
136
135
  ]
137
136
  : [],
138
137
  ] }));
@@ -183,13 +182,15 @@ export function InvalidateCycleDialogButton(props) {
183
182
  return null;
184
183
  if (!canInvalidateMaterial(inProcMat))
185
184
  return null;
186
- const allowChange = possibleNew !== null &&
185
+ const allowChange = (lastMat === null || lastMat.process <= 1) &&
186
+ possibleNew !== null &&
187
187
  (!LazySeq.ofObject(possibleNew.possibleCastingsByQueue ?? {}).isEmpty() ||
188
188
  !LazySeq.ofObject(possibleNew.possibleJobsByQueue ?? {}).isEmpty());
189
189
  if (!allowChange && (lastMat === null || lastMat.process < 1))
190
190
  return null;
191
191
  if (props.ignoreOperator)
192
192
  operator = null;
193
+ const changingAssignment = props.st !== null && (props.st.changeRawMat !== null || props.st.changeJobUnique !== null);
193
194
  function showInvalidate() {
194
195
  props.setState({
195
196
  process: null,
@@ -230,17 +231,20 @@ export function InvalidateCycleDialogButton(props) {
230
231
  }
231
232
  }
232
233
  return (_jsxs(_Fragment, { children: [props.st === null ? (_jsx(Button, { color: "primary", onClick: showInvalidate, children: "Invalidate Cycle" })) : undefined, props.st !== null ? (_jsx(Button, { color: "primary", onClick: invalidateCycle, disabled: props.st.updating ||
234
+ (changingAssignment && !allowChange) ||
235
+ (!changingAssignment &&
236
+ props.st.process !== null &&
237
+ props.st.process !== lastMat?.process) ||
233
238
  (props.st.process === null &&
234
239
  props.st.changeRawMat === null &&
235
240
  props.st.changeJobUnique === null), children: props.st.changeJobUnique !== null
236
- ? "Invalidate Everything and Change to Job " + props.st.changeJobUnique
241
+ ? (lastMat?.process === 1
242
+ ? "Invalidate Process 1 and Change to Job "
243
+ : "Change to Job ") + props.st.changeJobUnique
237
244
  : props.st.changeRawMat !== null
238
- ? "Invalidate Everything and Change to " + props.st.changeRawMat
245
+ ? (lastMat?.process === 1 ? "Invalidate Process 1 and Change to " : "Change to ") +
246
+ props.st.changeRawMat
239
247
  : props.st.process !== null
240
- ? props.st.process === 1
241
- ? lastMat && lastMat.process > 1
242
- ? "Invalidate All Processes"
243
- : "Invalidate Process 1"
244
- : "Invalidate Process " + props.st.process.toString()
248
+ ? "Invalidate Process " + props.st.process.toString()
245
249
  : "Invalidate Cycle" })) : undefined] }));
246
250
  }
@@ -1,14 +1,9 @@
1
1
  import { ReactNode } from "react";
2
- import { SubmitBasketLoadStationCommand } from "./BasketLoadStationWork.js";
3
- import { SubmitBasketLocationCorrection, SubmitBasketMovementCompletion } from "./BasketMovementArrival.js";
4
2
  interface LoadStationProps {
5
3
  readonly loadNum: number;
6
4
  readonly queues: ReadonlyArray<string>;
7
5
  readonly completed: boolean;
8
6
  readonly whiteBackground?: boolean;
9
- readonly submitBasketLoadStationCommand?: SubmitBasketLoadStationCommand;
10
- readonly submitBasketMovementCompletion?: SubmitBasketMovementCompletion;
11
- readonly submitBasketLocationCorrection?: SubmitBasketLocationCorrection;
12
7
  }
13
8
  export declare function LoadStation(props: LoadStationProps): import("react").JSX.Element;
14
9
  declare function LoadStationCheckWidth(props: LoadStationProps): ReactNode;
@@ -31,7 +31,7 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31
31
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32
32
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
33
  */
34
- import { useMemo, memo, useState, useCallback, useEffect } from "react";
34
+ import { useMemo, memo, useState, useCallback } from "react";
35
35
  import { Box, useMediaQuery, Button, Typography } from "@mui/material";
36
36
  import { LazySeq, OrderedMap } from "@seedtactics/immutable-collections";
37
37
  import { FolderOpen as FolderOpenIcon } from "@mui/icons-material";
@@ -63,8 +63,7 @@ import { PrintLabelButton } from "./PrintedLabel.js";
63
63
  import { hideNonLoadingMaterialOnLoadStation } from "../../data/queue-material.js";
64
64
  import { basketDisplayName, loadStationDisplayName } from "../../cell-status/station-cycles.js";
65
65
  import { canAddOrMoveMaterialToQueue } from "../../data/material-operation-policy.js";
66
- import { BasketLoadStationWorkflow, } from "./BasketLoadStationWork.js";
67
- import { BasketMovementArrival, loadStationArrivalInstruction, } from "./BasketMovementArrival.js";
66
+ import { BasketLoadStationWorkflow } from "./BasketLoadStationWork.js";
68
67
  function encodeLoadStationRegionIdPart(value) {
69
68
  return encodeURIComponent(value.toString());
70
69
  }
@@ -393,7 +392,7 @@ function ElapsedLoadTime({ elapsedLoadTime }) {
393
392
  return null;
394
393
  }
395
394
  }
396
- function PalletFace({ data, faceNum, loadNum, submitBasketLoadStationCommand, }) {
395
+ function PalletFace({ data, faceNum }) {
397
396
  const face = data.face.get(faceNum);
398
397
  const fmsInfo = useAtomValue(fmsInformation);
399
398
  const basketName = basketDisplayName(fmsInfo.basketName);
@@ -408,7 +407,7 @@ function PalletFace({ data, faceNum, loadNum, submitBasketLoadStationCommand, })
408
407
  }, children: _jsx(ElapsedLoadTime, { elapsedLoadTime: data.elapsedLoadingTime }) })] })) : null, _jsx(Box, { sx: { ml: "4em", mr: "4em" }, children: _jsx(MoveMaterialArrowNode, { kind: {
409
408
  type: MoveMaterialNodeKindType.BasketZone,
410
409
  basketId: data.activeBasket.basketId,
411
- }, children: _jsx(BasketLoadStationWorkflow, { stationNumber: loadNum, basket: data.activeBasket, material: data.allMaterial, fsize: data.fsize, submitCommand: submitBasketLoadStationCommand }) }) })] }));
410
+ }, children: _jsx(BasketLoadStationWorkflow, { basket: data.activeBasket, material: data.allMaterial, fsize: data.fsize }) }) })] }));
412
411
  }
413
412
  if (!data.pallet)
414
413
  return null;
@@ -680,23 +679,6 @@ export function LoadStation(props) {
680
679
  const currentSt = useAtomValue(currentStatus);
681
680
  const hideNonLoading = useAtomValue(hideNonLoadingMaterialOnLoadStation);
682
681
  const data = useMemo(() => selectLoadStationAndQueueProps(props.loadNum, props.queues, currentSt, hideNonLoading), [currentSt, props.loadNum, props.queues, hideNonLoading]);
683
- const arrivalInstruction = loadStationArrivalInstruction(currentSt.basketMoveInstructions ?? [], props.loadNum);
684
- const [recentArrivalReceipt, setRecentArrivalReceipt] = useState();
685
- useEffect(() => {
686
- if (recentArrivalReceipt !== undefined &&
687
- (recentArrivalReceipt.stationNumber !== props.loadNum ||
688
- (arrivalInstruction !== undefined &&
689
- arrivalInstruction.instructionId !== recentArrivalReceipt.instruction.instructionId))) {
690
- // This state is an ephemeral receipt; discard it permanently when a new instruction supersedes it.
691
- // oxlint-disable-next-line react/set-state-in-effect -- intentional invalidation of stale UI state.
692
- setRecentArrivalReceipt(undefined);
693
- }
694
- }, [arrivalInstruction, props.loadNum, recentArrivalReceipt]);
695
- const displayedArrivalInstruction = arrivalInstruction ?? recentArrivalReceipt?.instruction;
696
- const displayedArrivalReceipt = recentArrivalReceipt?.stationNumber === props.loadNum &&
697
- displayedArrivalInstruction?.instructionId === recentArrivalReceipt?.instruction.instructionId
698
- ? recentArrivalReceipt
699
- : undefined;
700
682
  const queueCols = LazySeq.of(data.queues)
701
683
  .sortBy(([q]) => q)
702
684
  .map(([q, mats]) => ({
@@ -743,53 +725,31 @@ export function LoadStation(props) {
743
725
  horizontal: fillViewPort,
744
726
  showMatInCompleted: props.completed,
745
727
  });
746
- return (_jsxs(_Fragment, { children: [displayedArrivalInstruction === undefined ? null : (_jsx(BasketMovementArrival, { basketName: basketName, instruction: displayedArrivalInstruction, stationNumber: props.loadNum, submitCommand: props.submitBasketMovementCompletion, submitCorrection: props.submitBasketLocationCorrection, receipt: displayedArrivalReceipt, onAccepted: setRecentArrivalReceipt, onCorrected: (command) => setRecentArrivalReceipt((current) => {
747
- if (current === undefined ||
748
- current.stationNumber !== props.loadNum ||
749
- current.receipt.observationId !== command.targetObservationId)
750
- return current;
751
- return command.replacementBasketId === null ||
752
- command.replacementObservationId === null
753
- ? { ...current, status: "retracted" }
754
- : {
755
- ...current,
756
- receipt: {
757
- ...current.receipt,
758
- observationId: command.replacementObservationId,
759
- observedBasketId: command.replacementBasketId,
760
- },
761
- command: {
762
- ...current.command,
763
- commandId: command.replacementObservationId,
764
- observedBasketId: command.replacementBasketId,
765
- },
766
- status: "corrected",
767
- };
768
- }) })), _jsx(MoveMaterialArrowContainer, { hideArrows: !fillViewPort, whiteBackground: props.whiteBackground, children: _jsxs(Box, { component: "main", sx: {
769
- width: "100%",
770
- display: "grid",
771
- gridTemplate: grid,
772
- minHeight: {
773
- xs: "calc(100vh - 64px - 32px)",
774
- sm: "calc(100vh - 64px - 40px)",
775
- md: "calc(100vh - 64px)",
776
- },
777
- padding: fillViewPort ? undefined : "8px",
778
- }, children: [matCols.map((col, idx) => (_jsx(Box, { "data-testid": materialColumnTestId(col), sx: {
779
- gridArea: `mat${idx}`,
780
- padding: "8px",
781
- borderRight: fillViewPort ? "1px solid black" : undefined,
782
- borderBottom: !fillViewPort ? "1px solid black" : undefined,
783
- }, children: _jsx(MaterialColumn, { data: data, region: col }) }, idx))), data.face.keysToAscLazySeq().map((faceNum, idx) => (_jsx(Box, { sx: {
784
- marginLeft: "15px",
785
- gridArea: `palface${idx}`,
786
- padding: "8px",
787
- borderTop: data.pallet && idx !== 0 ? "1px solid black" : undefined,
788
- }, children: _jsx(PalletFace, { data: data, faceNum: faceNum, loadNum: props.loadNum, submitBasketLoadStationCommand: props.submitBasketLoadStationCommand }) }, faceNum))), _jsx(Box, { "data-testid": "load-station-completed", sx: {
789
- borderLeft: fillViewPort ? "1px solid black" : undefined,
790
- borderTop: !fillViewPort ? "1px solid black" : undefined,
791
- gridArea: "completed",
792
- }, children: _jsx(CompletedCol, { fillViewPort: fillViewPort, showMaterial: props.completed }) }), _jsx(MultiInstructionButton, { loadData: data }), _jsx(SelectWorkorderDialog, {}), _jsx(SelectInspTypeDialog, {}), _jsx(LoadMatDialog, { pallet: data.pallet?.palletNum ?? null, queues: props.queues })] }) })] }));
728
+ return (_jsx(_Fragment, { children: _jsx(MoveMaterialArrowContainer, { hideArrows: !fillViewPort, whiteBackground: props.whiteBackground, children: _jsxs(Box, { component: "main", sx: {
729
+ width: "100%",
730
+ display: "grid",
731
+ gridTemplate: grid,
732
+ minHeight: {
733
+ xs: "calc(100vh - 64px - 32px)",
734
+ sm: "calc(100vh - 64px - 40px)",
735
+ md: "calc(100vh - 64px)",
736
+ },
737
+ padding: fillViewPort ? undefined : "8px",
738
+ }, children: [matCols.map((col, idx) => (_jsx(Box, { "data-testid": materialColumnTestId(col), sx: {
739
+ gridArea: `mat${idx}`,
740
+ padding: "8px",
741
+ borderRight: fillViewPort ? "1px solid black" : undefined,
742
+ borderBottom: !fillViewPort ? "1px solid black" : undefined,
743
+ }, children: _jsx(MaterialColumn, { data: data, region: col }) }, idx))), data.face.keysToAscLazySeq().map((faceNum, idx) => (_jsx(Box, { sx: {
744
+ marginLeft: "15px",
745
+ gridArea: `palface${idx}`,
746
+ padding: "8px",
747
+ borderTop: data.pallet && idx !== 0 ? "1px solid black" : undefined,
748
+ }, children: _jsx(PalletFace, { data: data, faceNum: faceNum }) }, faceNum))), _jsx(Box, { "data-testid": "load-station-completed", sx: {
749
+ borderLeft: fillViewPort ? "1px solid black" : undefined,
750
+ borderTop: !fillViewPort ? "1px solid black" : undefined,
751
+ gridArea: "completed",
752
+ }, children: _jsx(CompletedCol, { fillViewPort: fillViewPort, showMaterial: props.completed }) }), _jsx(MultiInstructionButton, { loadData: data }), _jsx(SelectWorkorderDialog, {}), _jsx(SelectInspTypeDialog, {}), _jsx(LoadMatDialog, { pallet: data.pallet?.palletNum ?? null, queues: props.queues })] }) }) }));
793
753
  }
794
754
  function LoadStationCheckWidth(props) {
795
755
  const fmsInfo = useAtomValue(fmsInformation);
@@ -49,7 +49,7 @@ import { useAtom, useAtomValue, useSetAtom } from "jotai";
49
49
  import { last30Rebookings } from "../../cell-status/rebookings.js";
50
50
  import { fmsInformation } from "../../network/server-settings.js";
51
51
  import { basketDisplayName, basketSlotLabel } from "../../cell-status/station-cycles.js";
52
- import { materialOperationState } from "../../data/material-operation-policy.js";
52
+ import { canAddOrMoveMaterialToQueue, canReorderQueuedMaterial, materialOperationState, } from "../../data/material-operation-policy.js";
53
53
  export class PartIdenticon extends PureComponent {
54
54
  render() {
55
55
  const iconSize = this.props.size || 50;
@@ -402,10 +402,12 @@ export const InProcMaterial = memo(function InProcMaterial(props) {
402
402
  return (_jsx(MatCard, { mat: inproc_mat_to_summary(props.mat), inProcMat: props.mat, displayActionForSinglePallet: props.displayActionForSinglePallet, fsize: props.fsize, hideAvatar: props.hideAvatar, displayJob: props.displayJob, showDragHandle: props.showHandle, hideEmptySerial: props.hideEmptySerial, showRawMaterial: props.showRawMaterial, showJobComment: props.showJobComment, shake: props.shake }));
403
403
  });
404
404
  export const SortableInProcMaterial = memo(function SortableInProcMaterial(props) {
405
+ const canDrag = canReorderQueuedMaterial(props.mat) || canAddOrMoveMaterialToQueue(props.mat);
405
406
  const d = { mat: props.mat };
406
407
  const { active, isDragging, attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, } = useSortable({
407
408
  id: props.mat.materialID,
408
409
  data: d,
410
+ disabled: { draggable: !canDrag },
409
411
  });
410
412
  const handleProps = {
411
413
  ...listeners,
@@ -421,7 +423,7 @@ export const SortableInProcMaterial = memo(function SortableInProcMaterial(props
421
423
  : undefined,
422
424
  transition: active !== null ? transition : undefined,
423
425
  };
424
- return (_jsx(MatCard, { ref: setNodeRef, dragRootProps: { style }, showDragHandle: true, dragHandleProps: handleProps, setDragHandleRef: setActivatorNodeRef, isActiveDrag: isDragging, mat: inproc_mat_to_summary(props.mat), inProcMat: props.mat, displayActionForSinglePallet: props.displayActionForSinglePallet, hideAvatar: props.hideAvatar, displayJob: props.displayJob, hideEmptySerial: props.hideEmptySerial, fsize: props.fsize, shake: active ? undefined : props.shake, showRawMaterial: props.showRawMaterial }));
426
+ return (_jsx(MatCard, { ref: setNodeRef, dragRootProps: { style }, showDragHandle: canDrag, dragHandleProps: handleProps, setDragHandleRef: setActivatorNodeRef, isActiveDrag: isDragging, mat: inproc_mat_to_summary(props.mat), inProcMat: props.mat, displayActionForSinglePallet: props.displayActionForSinglePallet, hideAvatar: props.hideAvatar, displayJob: props.displayJob, hideEmptySerial: props.hideEmptySerial, fsize: props.fsize, shake: active ? undefined : props.shake, showRawMaterial: props.showRawMaterial }));
425
427
  });
426
428
  export function DragOverlayInProcMaterial(props) {
427
429
  return (_jsx(MatCard, { mat: inproc_mat_to_summary(props.mat), inProcMat: props.mat, displayActionForSinglePallet: props.displayActionForSinglePallet, showDragHandle: true, hideAvatar: props.hideAvatar, displayJob: props.displayJob, fsize: props.fsize, hideEmptySerial: props.hideEmptySerial, isDragOverlay: true }));
@@ -40,15 +40,16 @@ import { currentOperator } from "../../data/operators.js";
40
40
  import { ApiException, LocType, QueueRole, } from "../../network/api.js";
41
41
  import { fmsInformation } from "../../network/server-settings.js";
42
42
  import { useAtomValue, useSetAtom } from "jotai";
43
- import { canDirectlyQuarantine, canRemoveFromQueue, canSignalQuarantine, isActiveLoadStationOperation, } from "../../data/material-operation-policy.js";
43
+ import { canDirectlyQuarantine, canRemoveFromQueue, canSignalQuarantine, quarantineRoute, } from "../../data/material-operation-policy.js";
44
44
  function hasSupportedQuarantineExit(material, status) {
45
45
  const job = status.jobs[material.jobUnique];
46
46
  if (!job)
47
47
  return false;
48
- const path = job.procsAndPaths[material.process - 1]?.paths[material.path - 1];
48
+ const route = quarantineRoute(material);
49
+ const path = job.procsAndPaths[route.process - 1]?.paths[route.path - 1];
49
50
  if (!path)
50
51
  return false;
51
- return material.process === job.procsAndPaths.length || Boolean(path.outputQueue);
52
+ return route.process === job.procsAndPaths.length || Boolean(path.outputQueue);
52
53
  }
53
54
  function useQuarantineMaterial() {
54
55
  const fmsInfo = useAtomValue(fmsInformation);
@@ -56,8 +57,6 @@ function useQuarantineMaterial() {
56
57
  const curSt = useAtomValue(currentStatus);
57
58
  if (inProcMat === null || inProcMat.materialID < 0)
58
59
  return null;
59
- if (isActiveLoadStationOperation(inProcMat))
60
- return null;
61
60
  if (inProcMat.quarantineAfterUnload)
62
61
  return null;
63
62
  const quarantineQueue = fmsInfo.quarantineQueue?.length ? fmsInfo.quarantineQueue : null;
@@ -80,7 +79,8 @@ function useQuarantineMaterial() {
80
79
  .filter(([qname]) => !activeQueues.has(qname))
81
80
  .toRSet(([qname]) => qname);
82
81
  // If in a quarantine queue, allow removal from system
83
- if (inProcMat.location.type === LocType.InQueue &&
82
+ if (canRemoveFromQueue(inProcMat) &&
83
+ inProcMat.location.type === LocType.InQueue &&
84
84
  inProcMat.location.currentQueue &&
85
85
  quarantineQueues.has(inProcMat.location.currentQueue)) {
86
86
  return {
@@ -91,25 +91,16 @@ function useQuarantineMaterial() {
91
91
  };
92
92
  }
93
93
  let type = null;
94
- switch (inProcMat.location.type) {
95
- case LocType.OnPallet:
96
- case LocType.InBasket:
97
- if (!canSignalQuarantine(inProcMat) || !hasSupportedQuarantineExit(inProcMat, curSt)) {
98
- return null;
99
- }
100
- type = "SignalForScrap";
101
- break;
102
- case LocType.InQueue:
103
- if (!canDirectlyQuarantine(inProcMat))
104
- return null;
105
- if (inProcMat.location.currentQueue === undefined ||
106
- quarantineQueues.has(inProcMat.location.currentQueue)) {
107
- return null;
108
- }
109
- type = "Scrap";
110
- break;
111
- case LocType.Free:
94
+ if (canSignalQuarantine(inProcMat)) {
95
+ if (!hasSupportedQuarantineExit(inProcMat, curSt))
96
+ return null;
97
+ type = "SignalForScrap";
98
+ }
99
+ else if (canDirectlyQuarantine(inProcMat)) {
100
+ if (inProcMat.location.currentQueue === undefined ||
101
+ quarantineQueues.has(inProcMat.location.currentQueue))
112
102
  return null;
103
+ type = "Scrap";
113
104
  }
114
105
  if (type) {
115
106
  return {
@@ -214,7 +205,7 @@ export function QuarantineMatButton({ onClose, ignoreOperator, }) {
214
205
  title = displayedSnapshot.destination
215
206
  ? `The current automated operation will continue. When the material leaves automation control, move it to ${displayedSnapshot.destination}`
216
207
  : "The current automated operation will continue. When the material leaves automation control, remove it from normal production flow as scrap";
217
- btnTxt = displayedSnapshot.destination ? "Quarantine" : "Scrap";
208
+ btnTxt = displayedSnapshot.destination ? "Signal for quarantine" : "Scrap";
218
209
  break;
219
210
  }
220
211
  async function quarantine() {
@@ -266,5 +257,5 @@ export function QuarantineMatButton({ onClose, ignoreOperator, }) {
266
257
  ? quarantiningQueued
267
258
  : removingFromQueue);
268
259
  const removeFromQueues = q?.canRemoveFromQueues === true ? () => removeFromQueue(q.material.materialID, operator) : null;
269
- return (_jsxs(_Fragment, { children: [q ? (_jsx(Tooltip, { title: title, children: _jsx(Button, { color: "primary", disabled: removing, onClick: openQuarantine, children: btnTxt }) })) : null, removeFromQueues && q ? (_jsx(RemoveFromQueuesButton, { material: q.material, removeFromQueues: removeFromQueues, removing: removingFromQueue })) : null, _jsxs(Dialog, { open: open && snapshot !== null, onClose: closeQuarantine, children: [_jsx(DialogTitle, { children: "Quarantine Material" }), _jsxs(DialogContent, { children: [_jsx("p", { children: title }), _jsx(TextField, { label: "Reason", value: reason, onChange: (e) => setReason(e.target.value), fullWidth: true, autoFocus: true, multiline: true }), error ? _jsx("p", { role: "alert", children: error }) : null] }), _jsxs(DialogActions, { children: [_jsx(Button, { color: "primary", disabled: removing, onClick: quarantine, children: btnTxt }), _jsx(Button, { color: "secondary", disabled: removing, onClick: closeQuarantine, children: "Cancel" })] })] })] }));
260
+ return (_jsxs(_Fragment, { children: [q ? (_jsx(Tooltip, { title: title, describeChild: displayedSnapshot.operation === "DeferredQuarantine", children: _jsx(Button, { color: "primary", disabled: removing, onClick: openQuarantine, children: btnTxt }) })) : null, removeFromQueues && q ? (_jsx(RemoveFromQueuesButton, { material: q.material, removeFromQueues: removeFromQueues, removing: removingFromQueue })) : null, _jsxs(Dialog, { open: open && snapshot !== null, onClose: closeQuarantine, children: [_jsx(DialogTitle, { children: "Quarantine Material" }), _jsxs(DialogContent, { children: [_jsx("p", { children: title }), _jsx(TextField, { label: "Reason", value: reason, onChange: (e) => setReason(e.target.value), fullWidth: true, autoFocus: true, multiline: true }), error ? _jsx("p", { role: "alert", children: error }) : null] }), _jsxs(DialogActions, { children: [_jsx(Button, { color: "primary", disabled: removing, onClick: quarantine, children: btnTxt }), _jsx(Button, { color: "secondary", disabled: removing, onClick: closeQuarantine, children: "Cancel" })] })] })] }));
270
261
  }