@seedtactics/insight-client 17.0.1-beta.12 → 17.0.1-beta.14

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 (45) hide show
  1. package/README.md +16 -4
  2. package/dist/assets/ProgramHighlight-CnZe9PDO.js +3 -0
  3. package/dist/assets/index-DWZg8nkq.js +367 -0
  4. package/dist/cell-status/current-status.d.ts +1 -0
  5. package/dist/cell-status/current-status.js +2 -0
  6. package/dist/cell-status/loading.d.ts +13 -0
  7. package/dist/cell-status/loading.js +36 -3
  8. package/dist/cell-status/material-details.d.ts +2 -2
  9. package/dist/cell-status/material-details.js +26 -13
  10. package/dist/components/App.d.ts +0 -5
  11. package/dist/components/App.js +1 -1
  12. package/dist/components/analysis/BufferChart.js +3 -2
  13. package/dist/components/operations/AllMaterial.js +9 -12
  14. package/dist/components/operations/RecentCycleChart.js +16 -21
  15. package/dist/components/station-monitor/BasketLoadStationWork.d.ts +1 -7
  16. package/dist/components/station-monitor/BasketLoadStationWork.js +9 -6
  17. package/dist/components/station-monitor/CancelLoadButton.js +2 -0
  18. package/dist/components/station-monitor/InvalidateCycle.js +24 -19
  19. package/dist/components/station-monitor/LoadStation.d.ts +0 -5
  20. package/dist/components/station-monitor/LoadStation.js +33 -72
  21. package/dist/components/station-monitor/Material.js +4 -2
  22. package/dist/components/station-monitor/PrintedLabel.js +15 -19
  23. package/dist/components/station-monitor/QuarantineButton.js +17 -26
  24. package/dist/components/station-monitor/SystemOverview.js +3 -2
  25. package/dist/components/station-monitor/Whiteboard.js +14 -15
  26. package/dist/data/material-operation-policy.d.ts +6 -0
  27. package/dist/data/material-operation-policy.js +26 -2
  28. package/dist/extension.d.ts +1 -1
  29. package/dist/extension.js +1 -0
  30. package/dist/index.html +2 -39
  31. package/dist/index.js +1 -6
  32. package/dist/network/api.d.ts +14 -34
  33. package/dist/network/api.js +62 -64
  34. package/dist/network/backend-mock.js +3 -0
  35. package/dist/network/backend.d.ts +1 -0
  36. package/dist/network/websocket.d.ts +1 -0
  37. package/dist/network/websocket.js +122 -36
  38. package/docs/material-quarantine.md +5 -4
  39. package/docs/mazak.md +9 -12
  40. package/docs/operator-procedures.md +14 -0
  41. package/package.json +22 -17
  42. package/dist/assets/ProgramHighlight-C6zSEXCX.js +0 -3
  43. package/dist/assets/index-kk-GRa5i.js +0 -388
  44. package/dist/components/station-monitor/BasketMovementArrival.d.ts +0 -40
  45. package/dist/components/station-monitor/BasketMovementArrival.js +0 -165
@@ -31,6 +31,8 @@ export declare class JobsClient {
31
31
  protected processRecent(response: Response): Promise<RecentHistoricData>;
32
32
  latestSchedule(signal?: AbortSignal): Promise<MostRecentSchedule>;
33
33
  protected processLatestSchedule(response: Response): Promise<MostRecentSchedule>;
34
+ completeBasketLoadStation(completion: BasketLoadStationCompletion, signal?: AbortSignal): Promise<void>;
35
+ protected processCompleteBasketLoadStation(response: Response): Promise<void>;
34
36
  currentStatus(signal?: AbortSignal): Promise<CurrentStatus>;
35
37
  protected processCurrentStatus(response: Response): Promise<CurrentStatus>;
36
38
  unscheduledRebookings(signal?: AbortSignal): Promise<Rebooking[]>;
@@ -710,7 +712,6 @@ export declare class CurrentStatus implements ICurrentStatus {
710
712
  baskets?: {
711
713
  [key: string]: BasketStatus;
712
714
  } | undefined;
713
- basketMoveInstructions?: BasketMoveInstruction[] | undefined;
714
715
  customState?: any | undefined;
715
716
  constructor(data?: ICurrentStatus);
716
717
  init(_data?: any): void;
@@ -735,7 +736,6 @@ export interface ICurrentStatus {
735
736
  baskets?: {
736
737
  [key: string]: BasketStatus;
737
738
  } | undefined;
738
- basketMoveInstructions?: BasketMoveInstruction[] | undefined;
739
739
  customState?: any | undefined;
740
740
  }
741
741
  export declare class HistoricJob extends Job implements IHistoricJob {
@@ -893,6 +893,7 @@ export declare enum LocType {
893
893
  }
894
894
  export declare class InProcessMaterialAction implements IInProcessMaterialAction {
895
895
  type: ActionType;
896
+ automatedTransfer?: boolean;
896
897
  workId?: string | undefined;
897
898
  loadCancellationId?: string | undefined;
898
899
  loadOntoPalletNum?: number | undefined;
@@ -916,6 +917,7 @@ export declare class InProcessMaterialAction implements IInProcessMaterialAction
916
917
  }
917
918
  export interface IInProcessMaterialAction {
918
919
  type: ActionType;
920
+ automatedTransfer?: boolean;
919
921
  workId?: string | undefined;
920
922
  loadCancellationId?: string | undefined;
921
923
  loadOntoPalletNum?: number | undefined;
@@ -1116,38 +1118,6 @@ export declare enum BasketLocationEnum {
1116
1118
  Storage = "Storage",
1117
1119
  InTransit = "InTransit"
1118
1120
  }
1119
- export declare class BasketMoveInstruction implements IBasketMoveInstruction {
1120
- instructionId: string;
1121
- basketId: number;
1122
- source?: BasketPosition | undefined;
1123
- destination: BasketPosition;
1124
- reason: BasketMoveReason;
1125
- displayText: string;
1126
- prerequisiteInstructionId?: string | undefined;
1127
- constructor(data?: IBasketMoveInstruction);
1128
- init(_data?: any): void;
1129
- static fromJS(data: any): BasketMoveInstruction;
1130
- toJSON(data?: any): any;
1131
- }
1132
- export interface IBasketMoveInstruction {
1133
- instructionId: string;
1134
- basketId: number;
1135
- source?: BasketPosition | undefined;
1136
- destination: BasketPosition;
1137
- reason: BasketMoveReason;
1138
- displayText: string;
1139
- prerequisiteInstructionId?: string | undefined;
1140
- }
1141
- export declare enum BasketMoveReason {
1142
- LoadMaterial = "LoadMaterial",
1143
- UnloadMaterial = "UnloadMaterial",
1144
- ProcessTransfer = "ProcessTransfer",
1145
- SupplyMaterialToCell = "SupplyMaterialToCell",
1146
- SupplyEmptyBasket = "SupplyEmptyBasket",
1147
- ReturnToStorage = "ReturnToStorage",
1148
- RemoveForCorrection = "RemoveForCorrection",
1149
- Other = "Other"
1150
- }
1151
1121
  export declare class ProblemDetails implements IProblemDetails {
1152
1122
  type?: string | undefined;
1153
1123
  title?: string | undefined;
@@ -1330,6 +1300,16 @@ export interface IRebooking {
1330
1300
  notes?: string | undefined;
1331
1301
  workorder?: string | undefined;
1332
1302
  }
1303
+ export declare class BasketLoadStationCompletion implements IBasketLoadStationCompletion {
1304
+ workId: string;
1305
+ constructor(data?: IBasketLoadStationCompletion);
1306
+ init(_data?: any): void;
1307
+ static fromJS(data: any): BasketLoadStationCompletion;
1308
+ toJSON(data?: any): any;
1309
+ }
1310
+ export interface IBasketLoadStationCompletion {
1311
+ workId: string;
1312
+ }
1333
1313
  export declare class SimulationResults implements ISimulationResults {
1334
1314
  scheduleId: string;
1335
1315
  jobs: Job[];
@@ -393,6 +393,40 @@ export class JobsClient {
393
393
  }
394
394
  return Promise.resolve(null);
395
395
  }
396
+ completeBasketLoadStation(completion, signal) {
397
+ let url_ = this.baseUrl + "/api/v1/jobs/basket-load-station/complete";
398
+ url_ = url_.replace(/[?&]$/, "");
399
+ const content_ = JSON.stringify(completion);
400
+ let options_ = {
401
+ body: content_,
402
+ method: "POST",
403
+ signal,
404
+ headers: {
405
+ "Content-Type": "application/json",
406
+ },
407
+ };
408
+ return this.http.fetch(url_, options_).then((_response) => {
409
+ return this.processCompleteBasketLoadStation(_response);
410
+ });
411
+ }
412
+ processCompleteBasketLoadStation(response) {
413
+ const status = response.status;
414
+ let _headers = {};
415
+ if (response.headers && response.headers.forEach) {
416
+ response.headers.forEach((v, k) => (_headers[k] = v));
417
+ }
418
+ if (status === 200) {
419
+ return response.text().then((_responseText) => {
420
+ return;
421
+ });
422
+ }
423
+ else if (status !== 200 && status !== 204) {
424
+ return response.text().then((_responseText) => {
425
+ return throwException("An unexpected server error occurred.", status, _responseText, _headers);
426
+ });
427
+ }
428
+ return Promise.resolve(null);
429
+ }
396
430
  currentStatus(signal) {
397
431
  let url_ = this.baseUrl + "/api/v1/jobs/status";
398
432
  url_ = url_.replace(/[?&]$/, "");
@@ -3625,11 +3659,6 @@ export class CurrentStatus {
3625
3659
  : new BasketStatus();
3626
3660
  }
3627
3661
  }
3628
- if (Array.isArray(_data["BasketMoveInstructions"])) {
3629
- this.basketMoveInstructions = [];
3630
- for (let item of _data["BasketMoveInstructions"])
3631
- this.basketMoveInstructions.push(BasketMoveInstruction.fromJS(item));
3632
- }
3633
3662
  this.customState = _data["CustomState"];
3634
3663
  }
3635
3664
  }
@@ -3700,11 +3729,6 @@ export class CurrentStatus {
3700
3729
  : undefined;
3701
3730
  }
3702
3731
  }
3703
- if (Array.isArray(this.basketMoveInstructions)) {
3704
- data["BasketMoveInstructions"] = [];
3705
- for (let item of this.basketMoveInstructions)
3706
- data["BasketMoveInstructions"].push(item ? item.toJSON() : undefined);
3707
- }
3708
3732
  data["CustomState"] = this.customState;
3709
3733
  return data;
3710
3734
  }
@@ -4050,6 +4074,7 @@ export class InProcessMaterialAction {
4050
4074
  init(_data) {
4051
4075
  if (_data) {
4052
4076
  this.type = _data["Type"];
4077
+ this.automatedTransfer = _data["AutomatedTransfer"];
4053
4078
  this.workId = _data["WorkId"];
4054
4079
  this.loadCancellationId = _data["LoadCancellationId"];
4055
4080
  this.loadOntoPalletNum = _data["LoadOntoPalletNum"];
@@ -4077,6 +4102,7 @@ export class InProcessMaterialAction {
4077
4102
  toJSON(data) {
4078
4103
  data = typeof data === "object" ? data : {};
4079
4104
  data["Type"] = this.type;
4105
+ data["AutomatedTransfer"] = this.automatedTransfer;
4080
4106
  data["WorkId"] = this.workId;
4081
4107
  data["LoadCancellationId"] = this.loadCancellationId;
4082
4108
  data["LoadOntoPalletNum"] = this.loadOntoPalletNum;
@@ -4497,60 +4523,6 @@ export var BasketLocationEnum;
4497
4523
  BasketLocationEnum["Storage"] = "Storage";
4498
4524
  BasketLocationEnum["InTransit"] = "InTransit";
4499
4525
  })(BasketLocationEnum || (BasketLocationEnum = {}));
4500
- export class BasketMoveInstruction {
4501
- constructor(data) {
4502
- if (data) {
4503
- for (var property in data) {
4504
- if (data.hasOwnProperty(property))
4505
- this[property] = data[property];
4506
- }
4507
- }
4508
- if (!data) {
4509
- this.destination = new BasketPosition();
4510
- }
4511
- }
4512
- init(_data) {
4513
- if (_data) {
4514
- this.instructionId = _data["InstructionId"];
4515
- this.basketId = _data["BasketId"];
4516
- this.source = _data["Source"] ? BasketPosition.fromJS(_data["Source"]) : undefined;
4517
- this.destination = _data["Destination"]
4518
- ? BasketPosition.fromJS(_data["Destination"])
4519
- : new BasketPosition();
4520
- this.reason = _data["Reason"];
4521
- this.displayText = _data["DisplayText"];
4522
- this.prerequisiteInstructionId = _data["PrerequisiteInstructionId"];
4523
- }
4524
- }
4525
- static fromJS(data) {
4526
- data = typeof data === "object" ? data : {};
4527
- let result = new BasketMoveInstruction();
4528
- result.init(data);
4529
- return result;
4530
- }
4531
- toJSON(data) {
4532
- data = typeof data === "object" ? data : {};
4533
- data["InstructionId"] = this.instructionId;
4534
- data["BasketId"] = this.basketId;
4535
- data["Source"] = this.source ? this.source.toJSON() : undefined;
4536
- data["Destination"] = this.destination ? this.destination.toJSON() : undefined;
4537
- data["Reason"] = this.reason;
4538
- data["DisplayText"] = this.displayText;
4539
- data["PrerequisiteInstructionId"] = this.prerequisiteInstructionId;
4540
- return data;
4541
- }
4542
- }
4543
- export var BasketMoveReason;
4544
- (function (BasketMoveReason) {
4545
- BasketMoveReason["LoadMaterial"] = "LoadMaterial";
4546
- BasketMoveReason["UnloadMaterial"] = "UnloadMaterial";
4547
- BasketMoveReason["ProcessTransfer"] = "ProcessTransfer";
4548
- BasketMoveReason["SupplyMaterialToCell"] = "SupplyMaterialToCell";
4549
- BasketMoveReason["SupplyEmptyBasket"] = "SupplyEmptyBasket";
4550
- BasketMoveReason["ReturnToStorage"] = "ReturnToStorage";
4551
- BasketMoveReason["RemoveForCorrection"] = "RemoveForCorrection";
4552
- BasketMoveReason["Other"] = "Other";
4553
- })(BasketMoveReason || (BasketMoveReason = {}));
4554
4526
  export class ProblemDetails {
4555
4527
  constructor(data) {
4556
4528
  if (data) {
@@ -5005,6 +4977,32 @@ export class Rebooking {
5005
4977
  return data;
5006
4978
  }
5007
4979
  }
4980
+ export class BasketLoadStationCompletion {
4981
+ constructor(data) {
4982
+ if (data) {
4983
+ for (var property in data) {
4984
+ if (data.hasOwnProperty(property))
4985
+ this[property] = data[property];
4986
+ }
4987
+ }
4988
+ }
4989
+ init(_data) {
4990
+ if (_data) {
4991
+ this.workId = _data["WorkId"];
4992
+ }
4993
+ }
4994
+ static fromJS(data) {
4995
+ data = typeof data === "object" ? data : {};
4996
+ let result = new BasketLoadStationCompletion();
4997
+ result.init(data);
4998
+ return result;
4999
+ }
5000
+ toJSON(data) {
5001
+ data = typeof data === "object" ? data : {};
5002
+ data["WorkId"] = this.workId;
5003
+ return data;
5004
+ }
5005
+ }
5008
5006
  export class SimulationResults {
5009
5007
  constructor(data) {
5010
5008
  if (data) {
@@ -229,6 +229,9 @@ export function registerMockBackend(offsetSeconds, mockD, mockEvts) {
229
229
  quarantineQueuedMaterial() {
230
230
  return Promise.resolve();
231
231
  },
232
+ completeBasketLoadStation() {
233
+ return Promise.resolve();
234
+ },
232
235
  cancelLoad() {
233
236
  return Promise.resolve();
234
237
  },
@@ -12,6 +12,7 @@ export interface JobAPI {
12
12
  addUnallocatedCastingToQueue(castingName: string, queue: string, qty: number, operatrorName: string | null, workorder: string | null, serials: string[]): Promise<ReadonlyArray<Readonly<api.IInProcessMaterial>>>;
13
13
  signalMaterialForQuarantine(materialId: number, operName: string | null, reason: string | undefined): Promise<void>;
14
14
  quarantineQueuedMaterial(materialId: number, operName: string | null, reason: string | undefined): Promise<void>;
15
+ completeBasketLoadStation(request: Readonly<api.IBasketLoadStationCompletion>): Promise<void>;
15
16
  cancelLoad(materialId: number, operName: string | null, request: Readonly<api.ICancelLoadRequest>): Promise<void>;
16
17
  invalidatePalletCycle(materialId: number, operName: string | null | undefined, changeCastingTo: string | null | undefined, changeJobUniqueTo: string | null | undefined, process: number): Promise<Readonly<api.IMaterialDetails> | null | undefined>;
17
18
  unscheduledRebookings(signal?: AbortSignal): Promise<ReadonlyArray<Readonly<api.IRebooking>> | null>;
@@ -1,4 +1,5 @@
1
1
  import { Atom } from "jotai";
2
2
  export declare const websocketReconnecting: Atom<boolean>;
3
+ export declare const currentStatusIsLive: Atom<boolean>;
3
4
  export declare const errorLoadingLast30: Atom<string | null>;
4
5
  export declare function WebsocketConnection(): null;
@@ -33,36 +33,90 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
33
  import { ServerEvent } from "./api.js";
34
34
  import { JobsBackend, LogBackend } from "./backend.js";
35
35
  import { fmsInformation } from "./server-settings.js";
36
- import { useCallback, useEffect, useRef } from "react";
37
- import { lastEventCounter, onLoadCurrentSt, onLoadLast30Jobs, onLoadLast30Log, onServerEvent, } from "../cell-status/loading.js";
36
+ import { useEffect, useRef } from "react";
37
+ import { lastEventCounter, onLoadCurrentSt, onLoadLast30Jobs, onLoadLast30Log, onLiveServerEvent, onHistoricalServerEvent, } from "../cell-status/loading.js";
38
38
  import { addDays } from "date-fns";
39
39
  import { last30SchIds } from "../cell-status/scheduled-jobs.js";
40
+ import { HashSet, OrderedMap } from "@seedtactics/immutable-collections";
40
41
  import { atom, useAtomValue, useSetAtom } from "jotai";
41
42
  const websocketReconnectingAtom = atom(false);
42
43
  export const websocketReconnecting = websocketReconnectingAtom;
44
+ // True while the websocket is open and this connection has loaded or received a current status, so
45
+ // the retained current status reflects the server. Loading the historical jobs and log does not
46
+ // affect it.
47
+ const currentStatusIsLiveRW = atom(false);
48
+ export const currentStatusIsLive = currentStatusIsLiveRW;
49
+ const websocketSessionAtom = atom({ id: 0, statusFromSocket: false });
50
+ // Only historical effects wait for HTTP catch-up. A fresh session discards its predecessor's
51
+ // unapplied buffer and reloads from lastEventCounter, which has not advanced past that history.
52
+ const bufferedLogAtom = atom(null);
53
+ // HTTP can include entries whose socket messages arrive after the response. Suppress those exact
54
+ // identities, never all counters below a maximum: live publication is not guaranteed to be ordered.
55
+ const loadedLogCountersAtom = atom(HashSet.empty());
43
56
  const errorLoadingLast30RW = atom(null);
44
- export const errorLoadingLast30 = errorLoadingLast30RW;
45
- function loadInitial(set) {
46
- const now = new Date();
47
- const thirtyDaysAgo = addDays(now, -30);
48
- const curStProm = JobsBackend.currentStatus().then((st) => set(onLoadCurrentSt, st));
49
- const jobsProm = JobsBackend.recent(thirtyDaysAgo, []).then((j) => set(onLoadLast30Jobs, j));
50
- const logProm = LogBackend.get(thirtyDaysAgo, now).then((log) => set(onLoadLast30Log, log));
51
- Promise.all([curStProm, jobsProm, logProm])
52
- .catch((e) => set(errorLoadingLast30RW, e.message ?? "Error"))
53
- .finally(() => set(websocketReconnectingAtom, false));
57
+ const errorLoadingLogRW = atom(null);
58
+ export const errorLoadingLast30 = atom((get) => get(errorLoadingLast30RW) ?? get(errorLoadingLogRW));
59
+ function isCurrentSession(get, session) {
60
+ return get(websocketSessionAtom).id === session;
61
+ }
62
+ function loadCurrentStatus(get, set, session) {
63
+ return JobsBackend.currentStatus().then((st) => {
64
+ const current = get(websocketSessionAtom);
65
+ if (current.id !== session || current.statusFromSocket)
66
+ return;
67
+ set(onLoadCurrentSt, st);
68
+ set(currentStatusIsLiveRW, true);
69
+ });
54
70
  }
55
- function loadMissed(lastCntr, schIds, set) {
71
+ function loadHistory(get, set, session, from, retryDelay = 1000) {
56
72
  const now = new Date();
57
- const curStProm = JobsBackend.currentStatus().then((st) => set(onLoadCurrentSt, st));
58
- const jobsProm = JobsBackend.recent(addDays(now, -30), schIds ? Array.from(schIds) : []).then((j) => set(onLoadLast30Jobs, j));
59
- const logProm = LogBackend.recent(lastCntr, undefined).then((log) => set(onLoadLast30Log, log));
60
- Promise.all([curStProm, jobsProm, logProm])
61
- .catch((e) => set(errorLoadingLast30RW, e.message ?? "Error"))
62
- .finally(() => set(websocketReconnectingAtom, false));
73
+ const request = from === null ? LogBackend.get(addDays(now, -30), now) : LogBackend.recent(from, undefined);
74
+ return request.then((log) => {
75
+ if (!isCurrentSession(get, session))
76
+ return;
77
+ const buffered = get(bufferedLogAtom) ?? OrderedMap.empty();
78
+ const merged = log.reduce((entries, entry) => entries.set(entry.counter, entry), buffered);
79
+ set(onLoadLast30Log, merged.valuesToAscLazySeq().toRArray());
80
+ set(loadedLogCountersAtom, HashSet.from(merged.keys()));
81
+ set(bufferedLogAtom, null);
82
+ set(errorLoadingLogRW, null);
83
+ }, (e) => {
84
+ if (!isCurrentSession(get, session))
85
+ return;
86
+ set(errorLoadingLogRW, e.message ?? "Error loading log history");
87
+ // Keep buffering and retry from the same applied position without interrupting live status.
88
+ setTimeout(() => {
89
+ if (isCurrentSession(get, session)) {
90
+ void loadHistory(get, set, session, from, Math.min(retryDelay * 2, 30000));
91
+ }
92
+ }, retryDelay);
93
+ });
94
+ }
95
+ function loadConnection(get, set, session) {
96
+ const from = get(lastEventCounter);
97
+ const schIds = get(last30SchIds);
98
+ const curStProm = loadCurrentStatus(get, set, session);
99
+ const jobsProm = JobsBackend.recent(addDays(new Date(), -30), from !== null && schIds ? Array.from(schIds) : []).then((j) => {
100
+ if (isCurrentSession(get, session))
101
+ set(onLoadLast30Jobs, j);
102
+ });
103
+ const logProm = loadHistory(get, set, session, from);
104
+ finishLoading([curStProm, jobsProm, logProm], get, set, session);
105
+ }
106
+ function finishLoading(loads, get, set, session) {
107
+ Promise.all(loads)
108
+ .catch((e) => {
109
+ if (isCurrentSession(get, session))
110
+ set(errorLoadingLast30RW, e.message ?? "Error");
111
+ })
112
+ .finally(() => {
113
+ if (isCurrentSession(get, session))
114
+ set(websocketReconnectingAtom, false);
115
+ });
63
116
  }
64
117
  class ReconnectingWebsocket {
65
118
  constructor(url) {
119
+ this.session = null;
66
120
  this.userCalledClose = false;
67
121
  this.reconnectAttempts = 0;
68
122
  this.url = url;
@@ -71,6 +125,12 @@ class ReconnectingWebsocket {
71
125
  close() {
72
126
  this.userCalledClose = true;
73
127
  this.ws?.close();
128
+ this.endSession(false);
129
+ }
130
+ endSession(reconnecting) {
131
+ const session = this.session;
132
+ this.session = null;
133
+ this.handleClose?.(session, reconnecting);
74
134
  }
75
135
  connect() {
76
136
  if (this.userCalledClose)
@@ -83,7 +143,7 @@ class ReconnectingWebsocket {
83
143
  localWs.addEventListener("open", () => {
84
144
  clearTimeout(connectTimeout);
85
145
  this.reconnectAttempts = 0;
86
- this.handleOpen?.();
146
+ this.session = this.handleOpen?.() ?? null;
87
147
  });
88
148
  localWs.addEventListener("close", () => {
89
149
  clearTimeout(connectTimeout);
@@ -91,7 +151,7 @@ class ReconnectingWebsocket {
91
151
  if (this.userCalledClose) {
92
152
  return;
93
153
  }
94
- this.handleReconnecting?.();
154
+ this.endSession(true);
95
155
  const delay = Math.min(1000 * Math.pow(1.5, this.reconnectAttempts), 30000);
96
156
  setTimeout(() => {
97
157
  this.reconnectAttempts++;
@@ -99,7 +159,7 @@ class ReconnectingWebsocket {
99
159
  }, delay);
100
160
  });
101
161
  localWs.addEventListener("message", (evt) => {
102
- this.handleMessage?.(evt);
162
+ this.handleMessage?.(evt, this.session);
103
163
  });
104
164
  // Browsers expose websocket failures as opaque events with no useful detail.
105
165
  // Reconnect handling is driven by close/open, so avoid polluting stderr by
@@ -107,25 +167,51 @@ class ReconnectingWebsocket {
107
167
  }
108
168
  }
109
169
  const onOpenAtom = atom(null, (get, set) => {
110
- const lastSeenCntr = get(lastEventCounter);
111
- const schIds = get(last30SchIds);
170
+ const session = get(websocketSessionAtom).id + 1;
171
+ set(websocketSessionAtom, { id: session, statusFromSocket: false });
112
172
  set(websocketReconnectingAtom, true);
173
+ set(currentStatusIsLiveRW, false);
113
174
  set(errorLoadingLast30RW, null);
114
- if (lastSeenCntr !== null && lastSeenCntr !== undefined) {
115
- loadMissed(lastSeenCntr, schIds, set);
116
- }
117
- else {
118
- loadInitial(set);
119
- }
175
+ set(errorLoadingLogRW, null);
176
+ set(bufferedLogAtom, OrderedMap.empty());
177
+ loadConnection(get, set, session);
178
+ return session;
120
179
  });
121
- const onMessageAtom = atom(null, (_, set, evt) => {
180
+ // Every close, including disposal, ends the socket's own session; only an unexpected close
181
+ // reconnects. A connection cannot end a newer connection's session.
182
+ const onCloseAtom = atom(null, (get, set, session, reconnecting) => {
183
+ if (reconnecting)
184
+ set(websocketReconnectingAtom, true);
185
+ if (session === null || !isCurrentSession(get, session))
186
+ return;
187
+ set(websocketSessionAtom, { id: session + 1, statusFromSocket: false });
188
+ set(currentStatusIsLiveRW, false);
189
+ set(bufferedLogAtom, null);
190
+ });
191
+ const onMessageAtom = atom(null, (get, set, evt, session) => {
192
+ if (session === null || !isCurrentSession(get, session))
193
+ return;
122
194
  const serverEvt = ServerEvent.fromJS(JSON.parse(evt.data));
123
- set(onServerEvent, { evt: serverEvt, now: new Date(), expire: true });
195
+ const eventAndTime = { evt: serverEvt, now: new Date(), expire: true };
196
+ set(onLiveServerEvent, eventAndTime);
197
+ const log = serverEvt.logEntry;
198
+ const buffered = get(bufferedLogAtom);
199
+ if (!log || !get(loadedLogCountersAtom).has(log.counter)) {
200
+ if (log && buffered !== null) {
201
+ set(bufferedLogAtom, buffered.set(log.counter, log));
202
+ }
203
+ else {
204
+ set(onHistoricalServerEvent, eventAndTime);
205
+ }
206
+ }
207
+ if (serverEvt.newCurrentStatus) {
208
+ set(websocketSessionAtom, { id: session, statusFromSocket: true });
209
+ set(currentStatusIsLiveRW, true);
210
+ }
124
211
  });
125
212
  export function WebsocketConnection() {
126
213
  const onOpen = useSetAtom(onOpenAtom);
127
- const setReconnecting = useSetAtom(websocketReconnectingAtom);
128
- const onReconnecting = useCallback(() => setReconnecting(true), [setReconnecting]);
214
+ const onClose = useSetAtom(onCloseAtom);
129
215
  const onMessage = useSetAtom(onMessageAtom);
130
216
  const fmsInfoLoadable = useAtomValue(fmsInformation);
131
217
  const websocketRef = useRef(null);
@@ -151,7 +237,7 @@ export function WebsocketConnection() {
151
237
  }
152
238
  const websocket = new ReconnectingWebsocket(uri);
153
239
  websocket.handleOpen = onOpen;
154
- websocket.handleReconnecting = onReconnecting;
240
+ websocket.handleClose = onClose;
155
241
  websocket.handleMessage = onMessage;
156
242
  websocketRef.current = websocket;
157
243
  return () => {
@@ -160,6 +246,6 @@ export function WebsocketConnection() {
160
246
  websocketRef.current = null;
161
247
  }
162
248
  };
163
- }, [fmsInfoLoadable, onMessage, onOpen, onReconnecting]);
249
+ }, [fmsInfoLoadable, onMessage, onOpen, onClose]);
164
250
  return null;
165
251
  }
@@ -36,10 +36,11 @@ The [operations](client-operations#material) and [quality](client-quality#quaran
36
36
  webpages allow supervisors, quality engineers and others to manually move material between the
37
37
  various quarantined queues, view all the material, add notes, re-introduce material to "active"
38
38
  queues, or scrap material. In addition, the [server configuration](server-config) contains a setting
39
- _QuarantineQueue_ for the initial quarantine queue. If this setting is given, the
40
- [station monitor webpages](client-station-monitor) will contain a button _Quarantine Material_: if
41
- pressed the material is moved out of the "active" whiteboard regions/queues and into the quarantine
42
- queue specified in the server configuration file.
39
+ _QuarantineQueue_ for the initial quarantine queue. Direct quarantine moves material that is waiting
40
+ in a queue into this configured queue. A quarantine signal instead records a request for the backend
41
+ to handle material when it leaves automation control; it does not immediately move material or
42
+ cancel a load. The [operator procedures](operator-procedures#quarantine-material) explain this
43
+ distinction. The available actions depend on the installation and the material's current state.
43
44
 
44
45
  FMS Insight allows arbitrary quarantined material queues, but we suggest you follow a scheme similar
45
46
  to the "TODO, In-Progress, Done" Kanban task management/project management technique.
package/docs/mazak.md CHANGED
@@ -2,16 +2,14 @@
2
2
  title: Mazak Palletech Integration
3
3
  nav: FMS Insight Server > Mazak
4
4
  description: >-
5
- FMS Insight works with all Palletech cell controller versions from Mazak: Version E, Web Version,
6
- and Smooth PMC. FMS Insight can read all events from the cell controller and can also edit almost
7
- all the data in the cell controller.
5
+ FMS Insight supports Mazak Palletech Web Version and Smooth/Neo PMC. FMS Insight can read all
6
+ events from the cell controller and can also edit almost all the data in the cell controller.
8
7
  ---
9
8
 
10
9
  # FMS Insight Mazak Palletech Integration
11
10
 
12
- FMS Insight works with all Palletech cell controller versions from Mazak: Version E, Web Version,
13
- and Smooth PMC. FMS Insight can read all events from the cell controller and can also edit almost
14
- all the data in the cell controller.
11
+ FMS Insight supports Mazak Palletech Web Version and Smooth/Neo PMC. FMS Insight can read all events
12
+ from the cell controller and can also edit almost all the data in the cell controller.
15
13
 
16
14
  ## Load station numbering
17
15
 
@@ -31,8 +29,7 @@ updating configuration.
31
29
  To facilitate the communication between FMS Insight and the Mazak cell controller, you must acquire
32
30
  a program called "Mazak Open Database Kit". This is a software program developed by Mazak which
33
31
  allows safe access to the data inside the cell controller. Please contact your Mazak representative
34
- and ask to obtain "Open Database Kit" that matches the specific cell controller (Version E, Web, or
35
- Smooth PMC).
32
+ and ask to obtain "Open Database Kit" that matches the specific cell controller (Web or Smooth PMC).
36
33
 
37
34
  ## Enable Log CSV
38
35
 
@@ -49,10 +46,10 @@ specify the same folder in the FMS Insight server configuration file.
49
46
 
50
47
  ## Load Instructions
51
48
 
52
- On Version E and MazakWeb, one parameter must be changed. If you are using Mazak Smooth PMC, this
53
- step can be skipped! Open the Mazak Palletech software, go to the parameter edit screen, select `X`,
54
- and scroll to the setting `X-31`. Set the `X-31` setting from 0 to 1. This setting will cause CSV
55
- files describing the current load and unload operation at the load station to be output to the
49
+ On MazakWeb, one parameter must be changed. If you are using Mazak Smooth PMC, this step can be
50
+ skipped! Open the Mazak Palletech software, go to the parameter edit screen, select `X`, and scroll
51
+ to the setting `X-31`. Set the `X-31` setting from 0 to 1. This setting will cause CSV files
52
+ describing the current load and unload operation at the load station to be output to the
56
53
  `c:\Mazak\FMS\LDS` directory. FMS Insight monitors this directory and uses the CSV files to display
57
54
  the parts being loaded and unloaded from each pallet at the load station.
58
55
 
@@ -69,6 +69,12 @@ detailed inspection, or some other rework. Material can be quarantined on the qu
69
69
  the material dialog and clicking a button. This moves the material to the quarantine queue where it
70
70
  is visible on the supervisor's material page.
71
71
 
72
+ A quarantine signal records a request for the backend to handle material when it leaves automation
73
+ control. The signal does not immediately move material or cancel a load. Direct quarantine moves
74
+ material that is waiting in a queue into the configured quarantine queue. The available actions
75
+ depend on the installation and the material's current state. Follow the displayed instructions and
76
+ the installation's procedure before moving physical material.
77
+
72
78
  Once the material is ready to be re-introduced into the cell, the operator should bring it back to
73
79
  the shop floor. Once the material has arrived, the operator can click a button on the queues webpage
74
80
  (or scan the barcode) to add the material into the queue. Once it is back in the queue, the cell
@@ -94,6 +100,14 @@ quarantine queue. That material can then be re-introduced back into the queue so
94
100
  FMS Insight matches the material on the shop floor. In addition, FMS Insight allows the ability to
95
101
  invalidate the inadvertent load events that were created.
96
102
 
103
+ Only the latest still-valid process can be invalidated. For material with recorded processes 1, 2,
104
+ and 3, first invalidate process 3, then review the refreshed history before invalidating process 2
105
+ in a separate action. Continue backwards one process at a time as needed. To change a casting or job
106
+ assignment, first invalidate any later processes in separate actions.
107
+
108
+ Invalidation affects whole recorded cycle events and all material listed in the confirmation. The
109
+ original events remain in the event log but no longer contribute to active material reconstruction.
110
+
97
111
  Insight does not reassign recorded pallet manufacturing history from one material identity to
98
112
  another. If machining was attributed to the wrong identity, record an explanatory operator note and
99
113
  use the installation's traceability exception procedure. Quarantine, invalidation, and readmission