tabletcommand-location 2.4.1 → 2.4.3

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 (65) hide show
  1. package/.claude/worktrees/sc-36275/.buildkite/pipeline.yml +5 -0
  2. package/.claude/worktrees/sc-36275/README.md +3 -0
  3. package/.claude/worktrees/sc-36275/build/audience.js +38 -0
  4. package/.claude/worktrees/sc-36275/build/audience.js.map +1 -0
  5. package/.claude/worktrees/sc-36275/build/index.js +95 -0
  6. package/.claude/worktrees/sc-36275/build/index.js.map +1 -0
  7. package/.claude/worktrees/sc-36275/build/location.js +278 -0
  8. package/.claude/worktrees/sc-36275/build/location.js.map +1 -0
  9. package/.claude/worktrees/sc-36275/build/query.js +378 -0
  10. package/.claude/worktrees/sc-36275/build/query.js.map +1 -0
  11. package/.claude/worktrees/sc-36275/build/store.js +182 -0
  12. package/.claude/worktrees/sc-36275/build/store.js.map +1 -0
  13. package/.claude/worktrees/sc-36275/build/test/location.js +522 -0
  14. package/.claude/worktrees/sc-36275/build/test/location.js.map +1 -0
  15. package/.claude/worktrees/sc-36275/build/test/mock.js +165 -0
  16. package/.claude/worktrees/sc-36275/build/test/mock.js.map +1 -0
  17. package/.claude/worktrees/sc-36275/build/test/query.js +174 -0
  18. package/.claude/worktrees/sc-36275/build/test/query.js.map +1 -0
  19. package/.claude/worktrees/sc-36275/build/types.js +8 -0
  20. package/.claude/worktrees/sc-36275/build/types.js.map +1 -0
  21. package/.claude/worktrees/sc-36275/cspell.json +64 -0
  22. package/.claude/worktrees/sc-36275/definitions/audience.d.ts +18 -0
  23. package/.claude/worktrees/sc-36275/definitions/audience.d.ts.map +1 -0
  24. package/.claude/worktrees/sc-36275/definitions/index.d.ts +11 -0
  25. package/.claude/worktrees/sc-36275/definitions/index.d.ts.map +1 -0
  26. package/.claude/worktrees/sc-36275/definitions/location.d.ts +32 -0
  27. package/.claude/worktrees/sc-36275/definitions/location.d.ts.map +1 -0
  28. package/.claude/worktrees/sc-36275/definitions/query.d.ts +36 -0
  29. package/.claude/worktrees/sc-36275/definitions/query.d.ts.map +1 -0
  30. package/.claude/worktrees/sc-36275/definitions/store.d.ts +64 -0
  31. package/.claude/worktrees/sc-36275/definitions/store.d.ts.map +1 -0
  32. package/.claude/worktrees/sc-36275/definitions/test/location.d.ts +2 -0
  33. package/.claude/worktrees/sc-36275/definitions/test/location.d.ts.map +1 -0
  34. package/.claude/worktrees/sc-36275/definitions/test/mock.d.ts +346 -0
  35. package/.claude/worktrees/sc-36275/definitions/test/mock.d.ts.map +1 -0
  36. package/.claude/worktrees/sc-36275/definitions/test/query.d.ts +2 -0
  37. package/.claude/worktrees/sc-36275/definitions/test/query.d.ts.map +1 -0
  38. package/.claude/worktrees/sc-36275/definitions/types.d.ts +53 -0
  39. package/.claude/worktrees/sc-36275/definitions/types.d.ts.map +1 -0
  40. package/.claude/worktrees/sc-36275/eslint.config.mjs +95 -0
  41. package/.claude/worktrees/sc-36275/gulpfile.js +23 -0
  42. package/.claude/worktrees/sc-36275/package-lock.json +9404 -0
  43. package/.claude/worktrees/sc-36275/package.json +54 -0
  44. package/.claude/worktrees/sc-36275/src/audience.ts +54 -0
  45. package/.claude/worktrees/sc-36275/src/index.ts +81 -0
  46. package/.claude/worktrees/sc-36275/src/location.ts +315 -0
  47. package/.claude/worktrees/sc-36275/src/query.ts +497 -0
  48. package/.claude/worktrees/sc-36275/src/store.ts +204 -0
  49. package/.claude/worktrees/sc-36275/src/test/location.ts +485 -0
  50. package/.claude/worktrees/sc-36275/src/test/mock.ts +186 -0
  51. package/.claude/worktrees/sc-36275/src/test/query.ts +209 -0
  52. package/.claude/worktrees/sc-36275/src/tsconfig.json +30 -0
  53. package/.claude/worktrees/sc-36275/src/types.ts +74 -0
  54. package/.claude/worktrees/sc-36275/test.sh +32 -0
  55. package/build/query.js +6 -1
  56. package/build/query.js.map +1 -1
  57. package/build/test/query.js +2 -2
  58. package/build/test/query.js.map +1 -1
  59. package/cspell.json +1 -0
  60. package/definitions/query.d.ts.map +1 -1
  61. package/definitions/test/mock.d.ts +5 -0
  62. package/definitions/test/mock.d.ts.map +1 -1
  63. package/package.json +7 -7
  64. package/src/query.ts +6 -1
  65. package/src/test/query.ts +2 -2
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "tabletcommand-location",
3
+ "version": "2.4.3",
4
+ "description": "Tablet Command Location",
5
+ "main": "build/index.js",
6
+ "scripts": {
7
+ "build": "gulp build",
8
+ "lint": "eslint src",
9
+ "test": "type-coverage && tsx --test --test-concurrency=1 --test-reporter=spec src/test/*.ts"
10
+ },
11
+ "author": "Joe Goeman <joe@tabletcommand.com>",
12
+ "license": "UNLICENSED",
13
+ "types": "definitions/index.d.ts",
14
+ "dependencies": {
15
+ "debug": "^4.4.3",
16
+ "lodash": "~4.18.1",
17
+ "tabletcommand-backend-models": "~7.4.112"
18
+ },
19
+ "peerDependencies": {
20
+ "mongoose": "^8.24.1"
21
+ },
22
+ "devDependencies": {
23
+ "@eslint/eslintrc": "^3.3.6",
24
+ "@eslint/js": "^9.39.5",
25
+ "@types/chai": "^5.2.3",
26
+ "@types/debug": "^4.1.13",
27
+ "@types/express": "^4.17.25",
28
+ "@types/lodash": "^4.17.25",
29
+ "@types/node": "^24.13.3",
30
+ "@typescript-eslint/eslint-plugin": "^8.69.0",
31
+ "@typescript-eslint/parser": "^8.69.0",
32
+ "chai": "^6.2.2",
33
+ "cspell": "^10.2.0",
34
+ "del": "^8.0.1",
35
+ "eslint": "^9.39.5",
36
+ "eslint-plugin-promise": "^7.3.0",
37
+ "eslint-plugin-security": "^4.0.1",
38
+ "globals": "^17.11.0",
39
+ "gulp": "^5.0.1",
40
+ "gulp-execa": "^8.0.1",
41
+ "mongoose": "^8.24.4",
42
+ "neostandard": "^0.13.0",
43
+ "tsx": "^4.23.13",
44
+ "type-coverage": "^2.30.1",
45
+ "typescript": "~5.9.3"
46
+ },
47
+ "typeCoverage": {
48
+ "atLeast": 99.3,
49
+ "ignoreCatch": true,
50
+ "ignoreFiles": [
51
+ "src/test/**/*.ts"
52
+ ]
53
+ }
54
+ }
@@ -0,0 +1,54 @@
1
+ import {
2
+ AccountIndustry,
3
+ } from "tabletcommand-backend-models";
4
+
5
+ export type AvlAudienceKind = "always" | "near" | "geo";
6
+
7
+ export interface AvlNearArea {
8
+ /** Radius around an anchor incident. Per-pair — deliberately not a shared constant. */
9
+ radiusMeters: number;
10
+ }
11
+
12
+ export type AvlAudienceEntry =
13
+ | { kind: "always" }
14
+ | { kind: "near"; area: AvlNearArea } // area required exactly when near
15
+ | { kind: "geo" }; // geometry comes from the OWNER's shareAVL.area
16
+
17
+ /** owner industry -> reader industry -> entry, or null for denied. Every pair present. */
18
+ export type AvlAudienceMatrix = Record<AccountIndustry, Record<AccountIndustry, AvlAudienceEntry | null>>;
19
+
20
+ const ALWAYS: AvlAudienceEntry = { kind: "always" };
21
+ // eslint-ignore-next-line
22
+ // const GEO: AvlAudienceEntry = { kind: "geo" };
23
+ const DENY = null;
24
+ const NEAR: AvlAudienceEntry = { kind: "near", area: { radiusMeters: 5000 } }; // 5km ~ 3.1mi
25
+
26
+ export const AVL_AUDIENCE_MATRIX: AvlAudienceMatrix = {
27
+ // owner => reader
28
+ [AccountIndustry.Fire]: {
29
+ [AccountIndustry.Fire]: ALWAYS,
30
+ [AccountIndustry.Law]: ALWAYS,
31
+ [AccountIndustry.Campus]: DENY,
32
+ [AccountIndustry.Utility]: NEAR,
33
+ },
34
+ [AccountIndustry.Law]: {
35
+ [AccountIndustry.Fire]: NEAR,
36
+ [AccountIndustry.Law]: ALWAYS,
37
+ [AccountIndustry.Campus]: DENY,
38
+ [AccountIndustry.Utility]: DENY,
39
+ },
40
+ [AccountIndustry.Campus]: {
41
+ [AccountIndustry.Fire]: ALWAYS,
42
+ [AccountIndustry.Law]: ALWAYS,
43
+ [AccountIndustry.Campus]: DENY, // deliberate — see above
44
+ [AccountIndustry.Utility]: DENY,
45
+ },
46
+ [AccountIndustry.Utility]: {
47
+ [AccountIndustry.Fire]: NEAR,
48
+ [AccountIndustry.Law]: DENY,
49
+ [AccountIndustry.Campus]: DENY,
50
+ [AccountIndustry.Utility]: ALWAYS,
51
+ },
52
+ };
53
+
54
+ // Unused/Incomplete
@@ -0,0 +1,81 @@
1
+ import locationModule from "./location";
2
+ import storeModule from "./store";
3
+ import {
4
+ DepartmentModel,
5
+ LocationModel,
6
+ CADVehicleModel,
7
+ DeviceMappingModel,
8
+ Department,
9
+ } from "tabletcommand-backend-models";
10
+ import _ from "lodash";
11
+ import {
12
+ LocationKindType,
13
+ LocationPayload,
14
+ LocationSource,
15
+ LocationVisibility,
16
+ } from "./types";
17
+
18
+ // Export types to external projects
19
+ export {
20
+ LocationKindType,
21
+ LocationPayload,
22
+ LocationSource,
23
+ LocationVisibility,
24
+ };
25
+
26
+ // When used externally
27
+ // import { query } from "tabletcommand-location";
28
+ // query.adminListLocations({ departmentId, movedAt });
29
+ export * as query from "./query";
30
+
31
+ export function lib(
32
+ departmentModel: DepartmentModel,
33
+ locationModel: LocationModel,
34
+ cadVehicleModel: CADVehicleModel,
35
+ deviceMappingModel: DeviceMappingModel
36
+ ) {
37
+ const store = storeModule(departmentModel, locationModel, cadVehicleModel, deviceMappingModel);
38
+ const location = locationModule(store);
39
+
40
+ async function processLocations(department: Partial<Department>, items: LocationPayload[] | null | undefined) {
41
+ const atDate = new Date();
42
+ try {
43
+ if (_.isUndefined(items) || _.isNull(items)) {
44
+ return [];
45
+ }
46
+ if (_.isObject(items) && !_.isArray(items)) {
47
+ items = [items];
48
+ }
49
+ const result = await location.processItems(department, items, atDate);
50
+ return result;
51
+ } catch (err) {
52
+ console.log("err processLocations: ", err);
53
+ throw err;
54
+ }
55
+ }
56
+
57
+ async function processLocationsDeviceMapping(department: Partial<Department>, items: LocationPayload[] | null | undefined) {
58
+ const atDate = new Date();
59
+ try {
60
+ if (_.isUndefined(items) || _.isNull(items)) {
61
+ return [];
62
+ }
63
+ if (_.isObject(items) && !_.isArray(items)) {
64
+ items = [items];
65
+ }
66
+ const result = await location.processItemsDeviceMappingGeotab(department, items, atDate);
67
+ return result;
68
+ } catch (err) {
69
+ console.log("err processLocations: ", err);
70
+ throw err;
71
+ }
72
+ }
73
+
74
+ return {
75
+ processLocations,
76
+ processLocationsDeviceMapping,
77
+ };
78
+ }
79
+
80
+ export default lib;
81
+ export type LibModule = ReturnType<typeof lib>;
@@ -0,0 +1,315 @@
1
+ import _ from "lodash";
2
+ import {
3
+ AccountIndustry,
4
+ Department,
5
+ Location,
6
+ CADVehicle,
7
+ LocationKindType,
8
+ LocationVisibility,
9
+ } from "tabletcommand-backend-models";
10
+ import {
11
+ LocationPayload,
12
+ DecodedLocation,
13
+ } from "./types";
14
+ import debugModule from "debug";
15
+ import { StoreModule } from "./store";
16
+
17
+ export function location(store: StoreModule) {
18
+ const debug = debugModule("tabletcommand-location:location");
19
+
20
+ function decodeLocation(item: LocationPayload, department: Partial<Department>, atDate: Date, deviceType: string): DecodedLocation {
21
+ let latitude = 0;
22
+ let longitude = 0;
23
+
24
+ if (_.isFinite(parseFloat(item.latitude.toString()))) {
25
+ latitude = parseFloat(item.latitude.toString());
26
+ }
27
+
28
+ if (_.isFinite(parseFloat(item.longitude.toString()))) {
29
+ longitude = parseFloat(item.longitude.toString());
30
+ }
31
+
32
+ if (!_.isString(item.radioName)) {
33
+ item.radioName = "";
34
+ }
35
+
36
+ if (!_.isString(item.vehicleId)) {
37
+ item.vehicleId = "";
38
+ }
39
+
40
+ const now = atDate.valueOf() / 1000.0;
41
+ // Don't save provided AVL time if it's more than 30s in the future
42
+ const futureTimeLimit = now + 30;
43
+ // If present, replace the modified unix date with the one provided by the CAD
44
+ let movedAtUnix = now;
45
+ if (_.isString(item.avlTime) && _.trim(item.avlTime) !== "") {
46
+ const checkISODate = (new Date(item.avlTime).valueOf()) / 1000.0;
47
+ const cadUnixTime = _.isNaN(checkISODate) && _.isString(item.avlTime) ? parseFloat(item.avlTime) : checkISODate;
48
+
49
+ if (_.isFinite(cadUnixTime) && cadUnixTime <= futureTimeLimit) {
50
+ movedAtUnix = cadUnixTime;
51
+ }
52
+ }
53
+
54
+ // Originally always was this fake id, but now can override these values
55
+ // If overridden then we use the values on the record instead of the fake userId
56
+ let userId = `${item.radioName}-${item.vehicleId}`; // Fake user id radioName - vehicleId
57
+ if (_.isString(item.userId) && _.trim(item.userId) !== "") {
58
+ userId = item.userId;
59
+ }
60
+ let session = userId;
61
+ if (_.isString(item.session) && _.trim(item.session) !== "") {
62
+ session = item.session;
63
+ }
64
+ let username = item.radioName;
65
+ if (_.isString(item.username) && _.trim(item.username) !== "") {
66
+ username = item.username;
67
+ }
68
+
69
+ let sharingEnabled = false;
70
+ let opAreaName = "";
71
+ let opAreaCode = "";
72
+ let deptState = "";
73
+ let locationStaleMinutes = 31 * 24 * 60; // 31d
74
+
75
+ if (_.isObject(department) && _.isObject(department.shareAVL)) {
76
+ sharingEnabled = department.shareAVL.enabled && department.accountType === "production";
77
+ opAreaName = department.shareAVL.opAreaName || "";
78
+ opAreaCode = department.shareAVL.opAreaCode || "";
79
+ }
80
+
81
+ if (_.isObject(department)) {
82
+ if (_.isObject(department.addressDetails) && _.isString(department.addressDetails.state)) {
83
+ deptState = department.addressDetails.state || "";
84
+ }
85
+ if (_.isNumber(department.locationStaleMinutes) && _.isFinite(department.locationStaleMinutes) && department.locationStaleMinutes > 0) {
86
+ locationStaleMinutes = department.locationStaleMinutes;
87
+ }
88
+ }
89
+ const deleteAfterDate = new Date(atDate.valueOf() + 1000 * 60 * locationStaleMinutes);
90
+ const personnelUUID = _.isString(item.personnelUUID) ? item.personnelUUID : "";
91
+ const transponderUUID = _.isString(item.transponderUUID) ? item.transponderUUID : "";
92
+ const industry: AccountIndustry = department?.industry ?? AccountIndustry.Fire; // If not set, fallback to fire
93
+
94
+ const cadAVL: Partial<Location> = {
95
+ device_type: deviceType,
96
+ source: item.source,
97
+ kindType: item.kindType,
98
+ locationGeoJSON: {
99
+ type: "Point",
100
+ coordinates: [longitude, latitude]
101
+ },
102
+ deleteAfterDate,
103
+ modified: atDate,
104
+ movedAt: new Date(movedAtUnix * 1000),
105
+ active: true,
106
+ username,
107
+ userId,
108
+ departmentId: String(department._id),
109
+ session,
110
+ version: 2,
111
+ shared: sharingEnabled,
112
+ sendToCAD: false,
113
+ opAreaName,
114
+ opAreaCode,
115
+ state: deptState,
116
+ visibility: [], // will be updated after .active is set
117
+ personnelUUID,
118
+ transponderUUID,
119
+ industry,
120
+ };
121
+
122
+ if (_.isNumber(item.speed)) {
123
+ const speed = item.speed;
124
+ cadAVL.speed = speed;
125
+ }
126
+
127
+ if (_.isNumber(item.heading) && item.heading >= 0 && item.heading <= 360) {
128
+ const heading = item.heading;
129
+ cadAVL.heading = heading;
130
+ }
131
+
132
+ if (_.isNumber(item.altitude)) {
133
+ const altitude = item.altitude;
134
+ cadAVL.altitude = altitude;
135
+ }
136
+
137
+ if (_.isString(item.kind)) {
138
+ cadAVL.kind = item.kind;
139
+ }
140
+ if (_.isString(item.type)) {
141
+ cadAVL.type = item.type;
142
+ }
143
+ if (_.isString(item.locationType)) {
144
+ cadAVL.locationType = item.locationType;
145
+ }
146
+ if (_.isObject(item.kindColor) &&
147
+ _.isString(item.kindColor.background) && item.kindColor.background !== "" &&
148
+ _.isString(item.kindColor.text) && item.kindColor.text !== "") {
149
+ cadAVL.kindColor = item.kindColor;
150
+ cadAVL.kindColorChangedAt = atDate;
151
+ }
152
+
153
+ const hasCoordinate = Math.abs(latitude) > 0.01 || Math.abs(longitude) > 0.01;
154
+ const validCoordinate = Math.abs(latitude) < 90 && Math.abs(longitude) < 180;
155
+ const hasVehicle = _.isString(item.vehicleId) && _.trim(item.vehicleId).length > 0;
156
+ const hasRadioName = _.isString(item.radioName) && _.trim(item.radioName).length > 0;
157
+ const isValid = hasCoordinate && validCoordinate && (hasVehicle || hasRadioName);
158
+
159
+ return {
160
+ cadAVL,
161
+ isValid,
162
+ };
163
+ }
164
+
165
+ async function processVehicle(item: LocationPayload, department: Partial<Department>, atDate: Date) {
166
+ const departmentId = String(department._id);
167
+ // need function to validate the item (As concerns vehicleId and radioName)
168
+ const isBidirectionalException = department.cadBidirectionalException || false;
169
+ // Determine if vehicle is active or not
170
+ const cadItem = await store.getVehicle(item, departmentId);
171
+ if (!_.isObject(cadItem) &&
172
+ department.cadOneWayVehiclesEnabled &&
173
+ (department.cadBidirectionalEnabled !== true || isBidirectionalException)) {
174
+ const vehicleItem: Partial<CADVehicle> = {
175
+ departmentId,
176
+ modifiedDate: atDate.valueOf() / 1000.0,
177
+ modified: atDate,
178
+ radioName: item.radioName,
179
+ vehicleId: item.vehicleId,
180
+ source: item.source,
181
+ };
182
+ await store.updateVehicle(vehicleItem, departmentId);
183
+ }
184
+ return cadItem;
185
+ }
186
+
187
+ // Returns true when the item is a person location and was fully handled here
188
+ // Returns false if trying to handle vehicle item
189
+ async function processPersonItem(decoded: DecodedLocation, item: LocationPayload, atDate: Date): Promise<boolean> {
190
+ const isPerson = decoded.cadAVL.kindType === LocationKindType.Person ||
191
+ decoded.cadAVL.locationType === LocationKindType.Person;
192
+ if (!isPerson) {
193
+ return false;
194
+ }
195
+ const hasPersonnelUUID = _.isString(decoded.cadAVL.personnelUUID) && decoded.cadAVL.personnelUUID !== "";
196
+ const hasTransponderUUID = _.isString(decoded.cadAVL.transponderUUID) && decoded.cadAVL.transponderUUID !== "";
197
+ if (!hasPersonnelUUID && !hasTransponderUUID) {
198
+ // A person Location with neither personnelUUID nor transponderUUID
199
+ // would leak into the vehicle sync endpoints (they filter on
200
+ // personnelUUID: "") so to process person must have one or other
201
+ debug(`skipping person location without personnelUUID/transponderUUID: ${JSON.stringify(item)}`);
202
+ return true;
203
+ }
204
+ // Person records: no CAD vehicle, always active/visible, never shared or CAD.
205
+ // Stamp kindType so records classified by locationType alone still match
206
+ // the person feed query (kindType: "person")
207
+ decoded.cadAVL.kindType = LocationKindType.Person;
208
+ decoded.cadAVL.active = true;
209
+ decoded.cadAVL.shared = false;
210
+ decoded.cadAVL.sendToCAD = false;
211
+ decoded.cadAVL.visibility = [LocationVisibility.Visible];
212
+ await store.updateLocation(decoded.cadAVL, atDate);
213
+ return true;
214
+ }
215
+
216
+ async function processItems(department: Partial<Department>, items: LocationPayload[], atDate: Date) {
217
+ for (const item of items) {
218
+ const decoded = decodeLocation(item, department, atDate, "cad");
219
+ debug(`processCADVehicleLocation item: ${JSON.stringify(item)} d:${JSON.stringify(department)} at: ${atDate.toISOString()}.`);
220
+ if (!decoded.isValid) {
221
+ // Skip only the invalid item, not the rest of the batch
222
+ continue;
223
+ }
224
+
225
+ if (await processPersonItem(decoded, item, atDate)) {
226
+ continue;
227
+ }
228
+
229
+ let active = true;
230
+ const cadItem = await processVehicle(item, department, atDate);
231
+
232
+ if (_.isObject(cadItem)) {
233
+ if (_.isBoolean(cadItem.mapHidden)) {
234
+ active = !cadItem.mapHidden;
235
+ }
236
+ }
237
+ // TODO: add support for location .kindType
238
+
239
+ decoded.cadAVL.active = active;
240
+ // Calculate .visibility after .active is set
241
+ const visibility: LocationVisibility[] = [
242
+ active ? LocationVisibility.Visible : LocationVisibility.Hidden,
243
+ ];
244
+ if (decoded.cadAVL.shared) {
245
+ visibility.push(LocationVisibility.Shared);
246
+ }
247
+ decoded.cadAVL.visibility = visibility;
248
+ await store.updateLocation(decoded.cadAVL, atDate);
249
+ }
250
+ }
251
+
252
+ async function processItemsDeviceMappingGeotab(department: Partial<Department>, items: LocationPayload[], atDate: Date) {
253
+ const departmentId = String(department._id);
254
+ for (const item of items) {
255
+ // TODO: may need to break decodeLocation out separately for device mapping, but works right now
256
+ const decoded = decodeLocation(item, department, atDate, "geotab");
257
+ debug(`processItemsDeviceMappingGeotab item: ${JSON.stringify(item)} d:${JSON.stringify(department)} at: ${atDate.toISOString()}.`);
258
+ if (!decoded.isValid) {
259
+ // Skip only the invalid item, not the rest of the batch
260
+ // This is actually a fix for vehicle processing as well
261
+ continue;
262
+ }
263
+
264
+ // Defensive code technically we shouldn't be getting here with person
265
+ // But it would still be valid to use this with a person so added the logic
266
+ if (await processPersonItem(decoded, item, atDate)) {
267
+ continue;
268
+ }
269
+
270
+ let active = true;
271
+ let locationToCAD = false;
272
+ const deviceMapping = await store.getDeviceMappingGeotab(item, departmentId);
273
+
274
+ if (_.isObject(deviceMapping)) {
275
+ if (_.isBoolean(deviceMapping.mapHidden)) {
276
+ active = !deviceMapping.mapHidden;
277
+ }
278
+ if (_.isBoolean(deviceMapping.locationToCAD)) {
279
+ locationToCAD = true;
280
+ }
281
+ if (_.isString(deviceMapping.mapId) && deviceMapping.mapId.trim() !== "") {
282
+ const radioName = deviceMapping.mapId;
283
+ const userId = `${radioName}-${item.vehicleId}`; // Fake user id radioName - vehicleId
284
+ decoded.cadAVL.username = radioName;
285
+ decoded.cadAVL.userId = userId;
286
+ decoded.cadAVL.session = userId;
287
+ }
288
+ }
289
+ // TODO: add support for location .kindType
290
+ decoded.cadAVL.active = active;
291
+ // Calculate .visibility after .active is set
292
+ const visibility: LocationVisibility[] = [
293
+ active ? LocationVisibility.Visible : LocationVisibility.Hidden,
294
+ ];
295
+ if (decoded.cadAVL.shared) {
296
+ visibility.push(LocationVisibility.Shared);
297
+ }
298
+ if (locationToCAD) {
299
+ decoded.cadAVL.sendToCAD = true;
300
+ visibility.push(LocationVisibility.CAD);
301
+ }
302
+ decoded.cadAVL.visibility = visibility;
303
+ await store.updateLocation(decoded.cadAVL, atDate);
304
+ }
305
+ }
306
+
307
+ return {
308
+ processVehicle,
309
+ processItems,
310
+ processItemsDeviceMappingGeotab,
311
+ };
312
+ }
313
+
314
+ export default location;
315
+ export type LocationModule = ReturnType<typeof location>;