ofsc-utility 1.0.38 → 1.0.40

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.
package/dist/index.d.ts CHANGED
@@ -14,7 +14,7 @@ export { generateAllOnHandInventoryOfAllResources, generateAllOnHandInventoryOfA
14
14
  export { getOAuthToken } from './oauthTokenService';
15
15
  export { downloadWorkZoneCSV } from './workZones';
16
16
  export { AllResources, downloadAllResourcesCSV, getworkSkillsOfResource } from './resources';
17
- export { downloadAllInactiveUsersCSV, downloadAllUsersCSV, generateUsersCollaborationCSV } from './users';
17
+ export { downloadAllInactiveUsers, downloadAllInactiveUsersCSV, downloadAllUsersCSV, generateUsersCollaborationCSV } from './users';
18
18
  export { downloadAllInventoryTypesCSV, getInventoryTypesDetail, updateCreateInventoryType } from './inventoryTypes';
19
19
  export { getActivitybyId, getAllActivities } from './activities';
20
20
  export { createActivityCustomerInventories, getActivityCustomerInventories } from './activityInventories';
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
36
36
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.createConfigurationFile = exports.createExcelFile = exports.downloadAllEventsOfLastOneHour = exports.downloadAllEventsOfDLastTwoMinutes = exports.downloadAllEventsOfDayCSV = exports.downloadAllEventsOfDay = exports.getActivityCustomerInventories = exports.createActivityCustomerInventories = exports.getAllActivities = exports.getActivitybyId = exports.updateCreateInventoryType = exports.getInventoryTypesDetail = exports.downloadAllInventoryTypesCSV = exports.generateUsersCollaborationCSV = exports.downloadAllUsersCSV = exports.downloadAllInactiveUsersCSV = exports.getworkSkillsOfResource = exports.downloadAllResourcesCSV = exports.AllResources = exports.downloadWorkZoneCSV = exports.getOAuthToken = exports.generateAllOnHandInventoryOfAllResourcesCSV = exports.generateAllOnHandInventoryOfAllResources = exports.WorkZone = exports.Utilities = exports.User = exports.Resource = exports.OauthTokenService = exports.CreateConfigurationFile = exports.InventoryType = exports.Inventory = exports.Events = exports.ActivityInventories = exports.Activity = void 0;
39
+ exports.createConfigurationFile = exports.createExcelFile = exports.downloadAllEventsOfLastOneHour = exports.downloadAllEventsOfDLastTwoMinutes = exports.downloadAllEventsOfDayCSV = exports.downloadAllEventsOfDay = exports.getActivityCustomerInventories = exports.createActivityCustomerInventories = exports.getAllActivities = exports.getActivitybyId = exports.updateCreateInventoryType = exports.getInventoryTypesDetail = exports.downloadAllInventoryTypesCSV = exports.generateUsersCollaborationCSV = exports.downloadAllUsersCSV = exports.downloadAllInactiveUsersCSV = exports.downloadAllInactiveUsers = exports.getworkSkillsOfResource = exports.downloadAllResourcesCSV = exports.AllResources = exports.downloadWorkZoneCSV = exports.getOAuthToken = exports.generateAllOnHandInventoryOfAllResourcesCSV = exports.generateAllOnHandInventoryOfAllResources = exports.WorkZone = exports.Utilities = exports.User = exports.Resource = exports.OauthTokenService = exports.CreateConfigurationFile = exports.InventoryType = exports.Inventory = exports.Events = exports.ActivityInventories = exports.Activity = void 0;
40
40
  // Export all methods grouped by category
41
41
  exports.Activity = __importStar(require("./activities"));
42
42
  exports.ActivityInventories = __importStar(require("./activityInventories"));
@@ -63,6 +63,7 @@ Object.defineProperty(exports, "AllResources", { enumerable: true, get: function
63
63
  Object.defineProperty(exports, "downloadAllResourcesCSV", { enumerable: true, get: function () { return resources_1.downloadAllResourcesCSV; } });
64
64
  Object.defineProperty(exports, "getworkSkillsOfResource", { enumerable: true, get: function () { return resources_1.getworkSkillsOfResource; } });
65
65
  var users_1 = require("./users");
66
+ Object.defineProperty(exports, "downloadAllInactiveUsers", { enumerable: true, get: function () { return users_1.downloadAllInactiveUsers; } });
66
67
  Object.defineProperty(exports, "downloadAllInactiveUsersCSV", { enumerable: true, get: function () { return users_1.downloadAllInactiveUsersCSV; } });
67
68
  Object.defineProperty(exports, "downloadAllUsersCSV", { enumerable: true, get: function () { return users_1.downloadAllUsersCSV; } });
68
69
  Object.defineProperty(exports, "generateUsersCollaborationCSV", { enumerable: true, get: function () { return users_1.generateUsersCollaborationCSV; } });
@@ -4,6 +4,8 @@ declare const OfscUserUtility: {
4
4
  generateUsersCollaborationCSV: any;
5
5
  downloadAllUsersCSV: typeof downloadAllUsersCSV;
6
6
  downloadAllInactiveUsersCSV: typeof downloadAllInactiveUsersCSV;
7
+ downloadAllInactiveUsers: typeof downloadAllInactiveUsers;
7
8
  };
8
9
  export declare function downloadAllInactiveUsersCSV(clientId: string, clientSecret: string, instanceUrl: string, inactivityThresholdDays?: number): Promise<void>;
10
+ export declare function downloadAllInactiveUsers(clientId: string, clientSecret: string, instanceUrl: string, inactivityThresholdDays?: number): Promise<Record<string, any>[]>;
9
11
  export default OfscUserUtility;
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.generateUsersCollaborationCSV = void 0;
40
40
  exports.downloadAllUsersCSV = downloadAllUsersCSV;
41
41
  exports.downloadAllInactiveUsersCSV = downloadAllInactiveUsersCSV;
42
+ exports.downloadAllInactiveUsers = downloadAllInactiveUsers;
42
43
  const fs = __importStar(require("fs"));
43
44
  const node_fetch_1 = __importDefault(require("node-fetch"));
44
45
  const index_1 = require("../oauthTokenService/index");
@@ -48,12 +49,12 @@ async function downloadAllUsersCSV(clientId, clientSecret, instanceUrl) {
48
49
  const limit = 100;
49
50
  let allItems = [];
50
51
  let totalFetched = 0;
51
- console.log("🚀 Starting users download...");
52
+ console.log("Starting users download...");
52
53
  console.log("-------------------------------------");
53
54
  while (true) {
54
55
  const url = `https://${instanceUrl}.fs.ocs.oraclecloud.com/rest/ofscCore/v1/users/?offset=${offset}&limit=${limit}`;
55
56
  const token = await (0, index_1.getOAuthToken)(clientId, clientSecret, instanceUrl);
56
- console.log(`➡️ Fetching offset=${offset} limit=${limit}`);
57
+ console.log(`Fetching offset=${offset} limit=${limit}`);
57
58
  const res = await (0, node_fetch_1.default)(url, {
58
59
  method: "GET",
59
60
  headers: {
@@ -62,18 +63,18 @@ async function downloadAllUsersCSV(clientId, clientSecret, instanceUrl) {
62
63
  }
63
64
  });
64
65
  if (!res.ok) {
65
- throw new Error(`❌ Fetch failed: ${res.status} ${res.statusText}`);
66
+ throw new Error(`Fetch failed: ${res.status} ${res.statusText}`);
66
67
  }
67
68
  const data = (await res.json());
68
69
  allItems.push(...data.items);
69
70
  totalFetched += data.items.length;
70
- console.log(` Received ${data.items.length} items (Total: ${totalFetched})`);
71
+ console.log(` Received ${data.items.length} items (Total: ${totalFetched})`);
71
72
  if (offset + limit >= data.totalResults)
72
73
  break;
73
74
  offset += limit;
74
75
  }
75
76
  console.log("-------------------------------------");
76
- console.log("🧩 Collecting all unique properties...");
77
+ console.log("Collecting all unique properties...");
77
78
  // Collect union of all properties
78
79
  const allProperties = new Set();
79
80
  for (const item of allItems) {
@@ -84,7 +85,7 @@ async function downloadAllUsersCSV(clientId, clientSecret, instanceUrl) {
84
85
  }
85
86
  }
86
87
  const headers = Array.from(allProperties);
87
- console.log(`📝 Total unique fields: ${headers.length}`);
88
+ console.log(`Total unique fields: ${headers.length}`);
88
89
  // Build CSV rows
89
90
  const csvRows = [];
90
91
  csvRows.push(headers.join(","));
@@ -109,11 +110,11 @@ async function downloadAllUsersCSV(clientId, clientSecret, instanceUrl) {
109
110
  const filePath = "./users.csv";
110
111
  fs.writeFileSync(filePath, csvRows.join("\n"));
111
112
  console.log("-------------------------------------");
112
- console.log("Users CSV Created Successfully!");
113
- console.log(`📁 File: ${filePath}`);
114
- console.log(`📦 Total Records: ${totalFetched}`);
115
- console.log(`🧩 Total Columns (Dynamic): ${headers.length}`);
116
- console.log(`🧩 Date Time: ${new Date()}`);
113
+ console.log("Users CSV Created Successfully!");
114
+ console.log(`File: ${filePath}`);
115
+ console.log(`Total Records: ${totalFetched}`);
116
+ console.log(`Total Columns (Dynamic): ${headers.length}`);
117
+ console.log(`Date Time: ${new Date()}`);
117
118
  console.log("-------------------------------------");
118
119
  }
119
120
  var collaborationGroups_1 = require("./collaborationGroups");
@@ -121,7 +122,8 @@ Object.defineProperty(exports, "generateUsersCollaborationCSV", { enumerable: tr
121
122
  const OfscUserUtility = {
122
123
  generateUsersCollaborationCSV: require('./collaborationGroups').generateUsersCollaborationCSV,
123
124
  downloadAllUsersCSV,
124
- downloadAllInactiveUsersCSV
125
+ downloadAllInactiveUsersCSV,
126
+ downloadAllInactiveUsers
125
127
  };
126
128
  /**
127
129
  * Returns true if the user is "inactive":
@@ -141,12 +143,12 @@ async function downloadAllInactiveUsersCSV(clientId, clientSecret, instanceUrl,
141
143
  const limit = 100;
142
144
  let allItems = [];
143
145
  let totalFetched = 0;
144
- console.log("🚀 Starting users download...");
146
+ console.log("Starting users download...");
145
147
  console.log("-------------------------------------");
146
148
  while (true) {
147
149
  const url = `https://${instanceUrl}.fs.ocs.oraclecloud.com/rest/ofscCore/v1/users/?offset=${offset}&limit=${limit}`;
148
150
  const token = await (0, index_1.getOAuthToken)(clientId, clientSecret, instanceUrl);
149
- console.log(`➡️ Fetching offset=${offset} limit=${limit}`);
151
+ console.log(`Fetching offset=${offset} limit=${limit}`);
150
152
  const res = await (0, node_fetch_1.default)(url, {
151
153
  method: "GET",
152
154
  headers: {
@@ -155,23 +157,23 @@ async function downloadAllInactiveUsersCSV(clientId, clientSecret, instanceUrl,
155
157
  }
156
158
  });
157
159
  if (!res.ok) {
158
- throw new Error(`❌ Fetch failed: ${res.status} ${res.statusText}`);
160
+ throw new Error(`Fetch failed: ${res.status} ${res.statusText}`);
159
161
  }
160
162
  const data = (await res.json());
161
163
  allItems.push(...data.items);
162
164
  totalFetched += data.items.length;
163
- console.log(` Received ${data.items.length} items (Total: ${totalFetched})`);
165
+ console.log(` Received ${data.items.length} items (Total: ${totalFetched})`);
164
166
  if (offset + limit >= data.totalResults)
165
167
  break;
166
168
  offset += limit;
167
169
  }
168
170
  console.log("-------------------------------------");
169
- console.log(`🔎 Filtering inactive users (no login in last ${inactivityThresholdDays} days)...`);
171
+ console.log(`Filtering inactive users (no login in last ${inactivityThresholdDays} days)...`);
170
172
  const now = new Date();
171
173
  const inactiveItems = allItems.filter(item => isInactiveUser(item, inactivityThresholdDays, now));
172
- console.log(` ${inactiveItems.length} of ${allItems.length} users are inactive`);
174
+ console.log(` ${inactiveItems.length} of ${allItems.length} users are inactive`);
173
175
  console.log("-------------------------------------");
174
- console.log("🧩 Collecting all unique properties...");
176
+ console.log("Collecting all unique properties...");
175
177
  // Collect union of all properties (from inactive users only)
176
178
  const allProperties = new Set();
177
179
  for (const item of inactiveItems) {
@@ -182,7 +184,7 @@ async function downloadAllInactiveUsersCSV(clientId, clientSecret, instanceUrl,
182
184
  }
183
185
  }
184
186
  const headers = Array.from(allProperties);
185
- console.log(`📝 Total unique fields: ${headers.length}`);
187
+ console.log(`Total unique fields: ${headers.length}`);
186
188
  // Build CSV rows
187
189
  const csvRows = [];
188
190
  csvRows.push(headers.join(","));
@@ -207,12 +209,87 @@ async function downloadAllInactiveUsersCSV(clientId, clientSecret, instanceUrl,
207
209
  const filePath = "./inactive_users.csv";
208
210
  fs.writeFileSync(filePath, csvRows.join("\n"));
209
211
  console.log("-------------------------------------");
210
- console.log("Inactive Users CSV Created Successfully!");
211
- console.log(`📁 File: ${filePath}`);
212
- console.log(`📦 Total Fetched: ${totalFetched}`);
213
- console.log(`🚫 Inactive Records (>${inactivityThresholdDays} days): ${inactiveItems.length}`);
214
- console.log(`🧩 Total Columns (Dynamic): ${headers.length}`);
215
- console.log(`🧩 Date Time: ${new Date()}`);
212
+ console.log("Inactive Users CSV Created Successfully!");
213
+ console.log(`File: ${filePath}`);
214
+ console.log(`Total Fetched: ${totalFetched}`);
215
+ console.log(`Inactive Records (>${inactivityThresholdDays} days): ${inactiveItems.length}`);
216
+ console.log(`Total Columns (Dynamic): ${headers.length}`);
217
+ console.log(`Date Time: ${new Date()}`);
216
218
  console.log("-------------------------------------");
217
219
  }
220
+ async function downloadAllInactiveUsers(clientId, clientSecret, instanceUrl, inactivityThresholdDays = 14) {
221
+ let offset = 0;
222
+ const limit = 100;
223
+ let allItems = [];
224
+ let totalFetched = 0;
225
+ console.log("Starting users download...");
226
+ console.log("-------------------------------------");
227
+ while (true) {
228
+ const url = `https://${instanceUrl}.fs.ocs.oraclecloud.com/rest/ofscCore/v1/users/?offset=${offset}&limit=${limit}`;
229
+ const token = await (0, index_1.getOAuthToken)(clientId, clientSecret, instanceUrl);
230
+ console.log(`Fetching offset=${offset} limit=${limit}`);
231
+ const res = await (0, node_fetch_1.default)(url, {
232
+ method: "GET",
233
+ headers: {
234
+ Authorization: `Bearer ${token}`,
235
+ Accept: "application/json"
236
+ }
237
+ });
238
+ if (!res.ok) {
239
+ throw new Error(`Fetch failed: ${res.status} ${res.statusText}`);
240
+ }
241
+ const data = (await res.json());
242
+ allItems.push(...data.items);
243
+ totalFetched += data.items.length;
244
+ console.log(` Received ${data.items.length} items (Total: ${totalFetched})`);
245
+ if (offset + limit >= data.totalResults)
246
+ break;
247
+ offset += limit;
248
+ }
249
+ console.log("-------------------------------------");
250
+ console.log(`Filtering inactive users (no login in last ${inactivityThresholdDays} days)...`);
251
+ const now = new Date();
252
+ // Skip users with blank/missing lastLoginTime (never-login users are ignored entirely)
253
+ const usersWithLogin = allItems.filter(item => !!(0, index_2.parseOFSCDate)(item.lastLoginTime));
254
+ const skippedNeverLoggedIn = allItems.length - usersWithLogin.length;
255
+ const inactiveItems = usersWithLogin.filter(item => isInactiveUser(item, inactivityThresholdDays, now));
256
+ console.log(` Skipped (never logged in / blank lastLoginTime): ${skippedNeverLoggedIn}`);
257
+ console.log(` ${inactiveItems.length} of ${usersWithLogin.length} users with login history are inactive`);
258
+ console.log("-------------------------------------");
259
+ console.log("Collecting all unique properties...");
260
+ // Collect union of all properties (from inactive users only)
261
+ const allProperties = new Set();
262
+ for (const item of inactiveItems) {
263
+ for (const key of Object.keys(item)) {
264
+ if (!["resources", "collaborationGroups", "resourceInternalIds", "links"].includes(key)) {
265
+ allProperties.add(key);
266
+ }
267
+ }
268
+ }
269
+ const headers = Array.from(allProperties);
270
+ console.log(`Total unique fields: ${headers.length}`);
271
+ // Build array of plain objects (one per inactive user)
272
+ const result = inactiveItems.map(item => {
273
+ const obj = {};
274
+ for (const field of headers) {
275
+ let value = item[field];
276
+ if (field === "keys") {
277
+ obj[field] = Array.isArray(value) ? value.join("|") : (value ?? "");
278
+ continue;
279
+ }
280
+ // Keep objects as objects (no JSON-stringify/escaping needed since this isn't CSV)
281
+ obj[field] = value !== undefined ? value : null;
282
+ }
283
+ return obj;
284
+ });
285
+ console.log("-------------------------------------");
286
+ console.log("Inactive Users Collected Successfully!");
287
+ console.log(`Total Fetched: ${totalFetched}`);
288
+ console.log(`Skipped (never logged in): ${skippedNeverLoggedIn}`);
289
+ console.log(`Inactive Records (>${inactivityThresholdDays} days): ${result.length}`);
290
+ console.log(`Total Columns (Dynamic): ${headers.length}`);
291
+ console.log(`Date Time: ${new Date()}`);
292
+ console.log("-------------------------------------");
293
+ return result;
294
+ }
218
295
  exports.default = OfscUserUtility;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ofsc-utility",
3
- "version": "1.0.38",
3
+ "version": "1.0.40",
4
4
  "description": "TypeScript helpers for Oracle Field Service Cloud (OFSC): events, resources, inventories, metadata and CSV/Excel utilities.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/readme.md CHANGED
@@ -144,8 +144,176 @@ await ofs.downloadAllUsersCSV(
144
144
  process.env.CLIENT_SECRET,
145
145
  process.env.INSTANCE_NAME,
146
146
  );
147
+
148
+ await ofs.downloadAllInactiveUsersCSV(
149
+ process.env.CLIENT_ID,
150
+ process.env.CLIENT_SECRET,
151
+ process.env.INSTANCE_NAME,
152
+ 14,
153
+ );
154
+ ```
155
+
156
+ #### Download inactive users
157
+
158
+ `downloadAllInactiveUsersCSV` downloads all users from the OFSC Users API, filters
159
+ the results, and writes the matching users to `./inactive_users.csv`.
160
+
161
+ A user is considered inactive when either:
162
+
163
+ - `lastLoginTime` is missing or cannot be parsed, or
164
+ - the time since `lastLoginTime` is greater than `inactivityThresholdDays`
165
+
166
+ The threshold defaults to `14` days. The comparison uses the current date and
167
+ time when the function runs. A user whose last login is exactly at the threshold
168
+ is not included because the function uses a strictly greater-than comparison.
169
+
170
+ ```js
171
+ const ofs = require("ofsc-utility");
172
+
173
+ async function downloadInactiveUsers() {
174
+ const clientId = process.env.CLIENT_ID;
175
+ const clientSecret = process.env.CLIENT_SECRET;
176
+ const instanceUrl = process.env.INSTANCE_NAME;
177
+
178
+ if (!clientId || !clientSecret || !instanceUrl) {
179
+ throw new Error(
180
+ "Set CLIENT_ID, CLIENT_SECRET and INSTANCE_NAME before running this example",
181
+ );
182
+ }
183
+
184
+ await ofs.downloadAllInactiveUsersCSV(
185
+ clientId,
186
+ clientSecret,
187
+ instanceUrl,
188
+ 14,
189
+ );
190
+ }
191
+
192
+ downloadInactiveUsers().catch((error) => {
193
+ console.error("Failed to download inactive users:", error);
194
+ process.exit(1);
195
+ });
196
+ ```
197
+
198
+ **Function signature**
199
+
200
+ ```ts
201
+ downloadAllInactiveUsersCSV(
202
+ clientId: string,
203
+ clientSecret: string,
204
+ instanceUrl: string,
205
+ inactivityThresholdDays?: number,
206
+ ): Promise<void>
207
+ ```
208
+
209
+ **Parameters**
210
+
211
+ - `clientId`: OFSC OAuth client ID.
212
+ - `clientSecret`: OFSC OAuth client secret.
213
+ - `instanceUrl`: OFSC instance name only, such as `mycompany`. Do not include
214
+ `https://` or `.fs.ocs.oraclecloud.com`.
215
+ - `inactivityThresholdDays`: optional non-negative number of days. Defaults to
216
+ `14`.
217
+
218
+ **Output**
219
+
220
+ - File: `./inactive_users.csv`, relative to the process working directory.
221
+ - The CSV contains one row per inactive user.
222
+ - Nested fields such as `resources`, `collaborationGroups`,
223
+ `resourceInternalIds`, and `links` are excluded.
224
+ - The remaining columns are generated dynamically from the fields returned by
225
+ the API. `keys` arrays are joined with `|`; other objects are serialized as
226
+ JSON strings.
227
+
228
+ The helper retrieves users in pages of 100 records and requires valid OFSC API
229
+ credentials. It does not return the CSV contents; successful completion resolves
230
+ to `void`.
231
+
232
+ To run the repository test script against a built distribution:
233
+
234
+ ```bash
235
+ npm run build
236
+ export CLIENT_ID=yourClientId
237
+ export CLIENT_SECRET=yourClientSecret
238
+ export INSTANCE_URL=yourInstanceName
239
+ export INACTIVITY_THRESHOLD_DAYS=14
240
+ node scripts/test-download-inactive-users.js
147
241
  ```
148
242
 
243
+ `INACTIVITY_THRESHOLD_DAYS` is optional in the test script and defaults to `14`.
244
+
245
+ #### Collect inactive users
246
+
247
+ `downloadAllInactiveUsers` downloads users from the OFSC Users API, filters out
248
+ users whose last login is within the inactivity threshold, and returns the
249
+ matching users as an array of plain objects. It does not create a CSV file.
250
+
251
+ Unlike `downloadAllInactiveUsersCSV`, users with a blank or unparsable
252
+ `lastLoginTime` are skipped. The threshold defaults to `14` days, and a user is
253
+ included only when the time since the last login is strictly greater than the
254
+ threshold.
255
+
256
+ ```js
257
+ const ofs = require("ofsc-utility");
258
+
259
+ async function collectInactiveUsers() {
260
+ const users = await ofs.User.downloadAllInactiveUsers(
261
+ process.env.CLIENT_ID,
262
+ process.env.CLIENT_SECRET,
263
+ process.env.INSTANCE_NAME,
264
+ 14,
265
+ );
266
+
267
+ console.log(`Collected ${users.length} inactive users`);
268
+ console.log(users[0]);
269
+ }
270
+
271
+ collectInactiveUsers().catch((error) => {
272
+ console.error("Failed to collect inactive users:", error);
273
+ process.exit(1);
274
+ });
275
+ ```
276
+
277
+ **Function signature**
278
+
279
+ ```ts
280
+ downloadAllInactiveUsers(
281
+ clientId: string,
282
+ clientSecret: string,
283
+ instanceUrl: string,
284
+ inactivityThresholdDays?: number,
285
+ ): Promise<Record<string, any>[]>
286
+ ```
287
+
288
+ **Parameters and result**
289
+
290
+ - `clientId`: OFSC OAuth client ID.
291
+ - `clientSecret`: OFSC OAuth client secret.
292
+ - `instanceUrl`: OFSC instance name only, such as `mycompany`. Do not include
293
+ `https://` or `.fs.ocs.oraclecloud.com`.
294
+ - `inactivityThresholdDays`: optional non-negative number of days. Defaults to
295
+ `14`.
296
+ - The returned array contains one object per inactive user.
297
+ - Nested fields such as `resources`, `collaborationGroups`,
298
+ `resourceInternalIds`, and `links` are excluded.
299
+ - `keys` arrays are joined with `|`; other object values remain objects.
300
+
301
+ The helper retrieves users in pages of 100 records and requires valid OFSC API
302
+ credentials.
303
+
304
+ To run the repository test script against a built distribution:
305
+
306
+ ```bash
307
+ npm run build
308
+ export CLIENT_ID=yourClientId
309
+ export CLIENT_SECRET=yourClientSecret
310
+ export INSTANCE_URL=yourInstanceName
311
+ export INACTIVITY_THRESHOLD_DAYS=14
312
+ node scripts/test-download-inactive-users-data.js
313
+ ```
314
+
315
+ `INACTIVITY_THRESHOLD_DAYS` is optional in the test script and defaults to `14`.
316
+
149
317
  ### Resource related methods
150
318
 
151
319
  ```js
@@ -285,6 +453,7 @@ Top-level exports include:
285
453
  - `downloadWorkZoneCSV`
286
454
  - `downloadAllResourcesCSV`
287
455
  - `downloadAllUsersCSV`
456
+ - `downloadAllInactiveUsersCSV`
288
457
  - `generateAllOnHandInventoryOfAllResourcesCSV`
289
458
  - `generateAllOnHandInventoryOfAllResources`
290
459
  - `downloadAllInventoryTypesCSV`