ofsc-utility 1.0.37 → 1.0.39

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 { downloadAllUsersCSV, generateUsersCollaborationCSV } from './users';
17
+ export { 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.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.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, "downloadAllInactiveUsersCSV", { enumerable: true, get: function () { return users_1.downloadAllInactiveUsersCSV; } });
66
67
  Object.defineProperty(exports, "downloadAllUsersCSV", { enumerable: true, get: function () { return users_1.downloadAllUsersCSV; } });
67
68
  Object.defineProperty(exports, "generateUsersCollaborationCSV", { enumerable: true, get: function () { return users_1.generateUsersCollaborationCSV; } });
68
69
  var inventoryTypes_1 = require("./inventoryTypes");
@@ -106,7 +106,7 @@ async function generateAllOnHandInventoryOfAllResources(clientId, clientSecret,
106
106
  console.log("--------------------------------------------------");
107
107
  // 2. Fetch on hand inventories for each resource
108
108
  const resourcesToProcess = limit
109
- ? allResources.slice(0, limit)
109
+ ? allResources.slice(-limit)
110
110
  : allResources;
111
111
  const rows = [];
112
112
  for (const [index, resource] of resourcesToProcess.entries()) {
@@ -3,5 +3,8 @@ export { generateUsersCollaborationCSV } from './collaborationGroups';
3
3
  declare const OfscUserUtility: {
4
4
  generateUsersCollaborationCSV: any;
5
5
  downloadAllUsersCSV: typeof downloadAllUsersCSV;
6
+ downloadAllInactiveUsersCSV: typeof downloadAllInactiveUsersCSV;
6
7
  };
8
+ export declare function downloadAllInactiveUsersCSV(clientId: string, clientSecret: string, instanceUrl: string, inactivityThresholdDays?: number): Promise<void>;
9
+ export declare function downloadAllInactiveUsers(clientId: string, clientSecret: string, instanceUrl: string, inactivityThresholdDays?: number): Promise<Record<string, any>[]>;
7
10
  export default OfscUserUtility;
@@ -38,20 +38,23 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.generateUsersCollaborationCSV = void 0;
40
40
  exports.downloadAllUsersCSV = downloadAllUsersCSV;
41
+ exports.downloadAllInactiveUsersCSV = downloadAllInactiveUsersCSV;
42
+ exports.downloadAllInactiveUsers = downloadAllInactiveUsers;
41
43
  const fs = __importStar(require("fs"));
42
44
  const node_fetch_1 = __importDefault(require("node-fetch"));
43
45
  const index_1 = require("../oauthTokenService/index");
46
+ const index_2 = require("../utilities/index");
44
47
  async function downloadAllUsersCSV(clientId, clientSecret, instanceUrl) {
45
48
  let offset = 0;
46
49
  const limit = 100;
47
50
  let allItems = [];
48
51
  let totalFetched = 0;
49
- console.log("🚀 Starting users download...");
52
+ console.log("Starting users download...");
50
53
  console.log("-------------------------------------");
51
54
  while (true) {
52
55
  const url = `https://${instanceUrl}.fs.ocs.oraclecloud.com/rest/ofscCore/v1/users/?offset=${offset}&limit=${limit}`;
53
56
  const token = await (0, index_1.getOAuthToken)(clientId, clientSecret, instanceUrl);
54
- console.log(`➡️ Fetching offset=${offset} limit=${limit}`);
57
+ console.log(`Fetching offset=${offset} limit=${limit}`);
55
58
  const res = await (0, node_fetch_1.default)(url, {
56
59
  method: "GET",
57
60
  headers: {
@@ -60,18 +63,18 @@ async function downloadAllUsersCSV(clientId, clientSecret, instanceUrl) {
60
63
  }
61
64
  });
62
65
  if (!res.ok) {
63
- throw new Error(`❌ Fetch failed: ${res.status} ${res.statusText}`);
66
+ throw new Error(`Fetch failed: ${res.status} ${res.statusText}`);
64
67
  }
65
68
  const data = (await res.json());
66
69
  allItems.push(...data.items);
67
70
  totalFetched += data.items.length;
68
- console.log(` Received ${data.items.length} items (Total: ${totalFetched})`);
71
+ console.log(` Received ${data.items.length} items (Total: ${totalFetched})`);
69
72
  if (offset + limit >= data.totalResults)
70
73
  break;
71
74
  offset += limit;
72
75
  }
73
76
  console.log("-------------------------------------");
74
- console.log("🧩 Collecting all unique properties...");
77
+ console.log("Collecting all unique properties...");
75
78
  // Collect union of all properties
76
79
  const allProperties = new Set();
77
80
  for (const item of allItems) {
@@ -82,7 +85,7 @@ async function downloadAllUsersCSV(clientId, clientSecret, instanceUrl) {
82
85
  }
83
86
  }
84
87
  const headers = Array.from(allProperties);
85
- console.log(`📝 Total unique fields: ${headers.length}`);
88
+ console.log(`Total unique fields: ${headers.length}`);
86
89
  // Build CSV rows
87
90
  const csvRows = [];
88
91
  csvRows.push(headers.join(","));
@@ -107,17 +110,185 @@ async function downloadAllUsersCSV(clientId, clientSecret, instanceUrl) {
107
110
  const filePath = "./users.csv";
108
111
  fs.writeFileSync(filePath, csvRows.join("\n"));
109
112
  console.log("-------------------------------------");
110
- console.log("Users CSV Created Successfully!");
111
- console.log(`📁 File: ${filePath}`);
112
- console.log(`📦 Total Records: ${totalFetched}`);
113
- console.log(`🧩 Total Columns (Dynamic): ${headers.length}`);
114
- 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()}`);
115
118
  console.log("-------------------------------------");
116
119
  }
117
120
  var collaborationGroups_1 = require("./collaborationGroups");
118
121
  Object.defineProperty(exports, "generateUsersCollaborationCSV", { enumerable: true, get: function () { return collaborationGroups_1.generateUsersCollaborationCSV; } });
119
122
  const OfscUserUtility = {
120
123
  generateUsersCollaborationCSV: require('./collaborationGroups').generateUsersCollaborationCSV,
121
- downloadAllUsersCSV
124
+ downloadAllUsersCSV,
125
+ downloadAllInactiveUsersCSV
122
126
  };
127
+ /**
128
+ * Returns true if the user is "inactive":
129
+ * - has no lastLoginTime at all, OR
130
+ * - lastLoginTime is older than thresholdDays
131
+ */
132
+ function isInactiveUser(item, thresholdDays, now = new Date()) {
133
+ const lastLogin = (0, index_2.parseOFSCDate)(item.lastLoginTime);
134
+ if (!lastLogin)
135
+ return true; // never logged in => treat as inactive
136
+ const diffMs = now.getTime() - lastLogin.getTime();
137
+ const diffDays = diffMs / (1000 * 60 * 60 * 24);
138
+ return diffDays > thresholdDays;
139
+ }
140
+ async function downloadAllInactiveUsersCSV(clientId, clientSecret, instanceUrl, inactivityThresholdDays = 14) {
141
+ let offset = 0;
142
+ const limit = 100;
143
+ let allItems = [];
144
+ let totalFetched = 0;
145
+ console.log("Starting users download...");
146
+ console.log("-------------------------------------");
147
+ while (true) {
148
+ const url = `https://${instanceUrl}.fs.ocs.oraclecloud.com/rest/ofscCore/v1/users/?offset=${offset}&limit=${limit}`;
149
+ const token = await (0, index_1.getOAuthToken)(clientId, clientSecret, instanceUrl);
150
+ console.log(`Fetching offset=${offset} limit=${limit}`);
151
+ const res = await (0, node_fetch_1.default)(url, {
152
+ method: "GET",
153
+ headers: {
154
+ Authorization: `Bearer ${token}`,
155
+ Accept: "application/json"
156
+ }
157
+ });
158
+ if (!res.ok) {
159
+ throw new Error(`Fetch failed: ${res.status} ${res.statusText}`);
160
+ }
161
+ const data = (await res.json());
162
+ allItems.push(...data.items);
163
+ totalFetched += data.items.length;
164
+ console.log(` Received ${data.items.length} items (Total: ${totalFetched})`);
165
+ if (offset + limit >= data.totalResults)
166
+ break;
167
+ offset += limit;
168
+ }
169
+ console.log("-------------------------------------");
170
+ console.log(`Filtering inactive users (no login in last ${inactivityThresholdDays} days)...`);
171
+ const now = new Date();
172
+ const inactiveItems = allItems.filter(item => isInactiveUser(item, inactivityThresholdDays, now));
173
+ console.log(` ${inactiveItems.length} of ${allItems.length} users are inactive`);
174
+ console.log("-------------------------------------");
175
+ console.log("Collecting all unique properties...");
176
+ // Collect union of all properties (from inactive users only)
177
+ const allProperties = new Set();
178
+ for (const item of inactiveItems) {
179
+ for (const key of Object.keys(item)) {
180
+ if (!["resources", "collaborationGroups", "resourceInternalIds", "links"].includes(key)) {
181
+ allProperties.add(key);
182
+ }
183
+ }
184
+ }
185
+ const headers = Array.from(allProperties);
186
+ console.log(`Total unique fields: ${headers.length}`);
187
+ // Build CSV rows
188
+ const csvRows = [];
189
+ csvRows.push(headers.join(","));
190
+ for (const item of inactiveItems) {
191
+ const row = headers.map(field => {
192
+ let value = item[field];
193
+ if (field === "keys") {
194
+ if (Array.isArray(value))
195
+ return `"${value.join("|")}"`;
196
+ if (value)
197
+ return `"${String(value)}"`;
198
+ return "";
199
+ }
200
+ // Objects → JSON-safe string
201
+ if (typeof value === "object" && value !== null) {
202
+ return `"${JSON.stringify(value).replace(/"/g, "'")}"`;
203
+ }
204
+ return value !== undefined ? `"${String(value).replace(/"/g, "'")}"` : "";
205
+ });
206
+ csvRows.push(row.join(","));
207
+ }
208
+ const filePath = "./inactive_users.csv";
209
+ fs.writeFileSync(filePath, csvRows.join("\n"));
210
+ console.log("-------------------------------------");
211
+ console.log("Inactive Users CSV Created Successfully!");
212
+ console.log(`File: ${filePath}`);
213
+ console.log(`Total Fetched: ${totalFetched}`);
214
+ console.log(`Inactive Records (>${inactivityThresholdDays} days): ${inactiveItems.length}`);
215
+ console.log(`Total Columns (Dynamic): ${headers.length}`);
216
+ console.log(`Date Time: ${new Date()}`);
217
+ console.log("-------------------------------------");
218
+ }
219
+ async function downloadAllInactiveUsers(clientId, clientSecret, instanceUrl, inactivityThresholdDays = 14) {
220
+ let offset = 0;
221
+ const limit = 100;
222
+ let allItems = [];
223
+ let totalFetched = 0;
224
+ console.log("Starting users download...");
225
+ console.log("-------------------------------------");
226
+ while (true) {
227
+ const url = `https://${instanceUrl}.fs.ocs.oraclecloud.com/rest/ofscCore/v1/users/?offset=${offset}&limit=${limit}`;
228
+ const token = await (0, index_1.getOAuthToken)(clientId, clientSecret, instanceUrl);
229
+ console.log(`Fetching offset=${offset} limit=${limit}`);
230
+ const res = await (0, node_fetch_1.default)(url, {
231
+ method: "GET",
232
+ headers: {
233
+ Authorization: `Bearer ${token}`,
234
+ Accept: "application/json"
235
+ }
236
+ });
237
+ if (!res.ok) {
238
+ throw new Error(`Fetch failed: ${res.status} ${res.statusText}`);
239
+ }
240
+ const data = (await res.json());
241
+ allItems.push(...data.items);
242
+ totalFetched += data.items.length;
243
+ console.log(` Received ${data.items.length} items (Total: ${totalFetched})`);
244
+ if (offset + limit >= data.totalResults)
245
+ break;
246
+ offset += limit;
247
+ }
248
+ console.log("-------------------------------------");
249
+ console.log(`Filtering inactive users (no login in last ${inactivityThresholdDays} days)...`);
250
+ const now = new Date();
251
+ // Skip users with blank/missing lastLoginTime (never-login users are ignored entirely)
252
+ const usersWithLogin = allItems.filter(item => !!(0, index_2.parseOFSCDate)(item.lastLoginTime));
253
+ const skippedNeverLoggedIn = allItems.length - usersWithLogin.length;
254
+ const inactiveItems = usersWithLogin.filter(item => isInactiveUser(item, inactivityThresholdDays, now));
255
+ console.log(` Skipped (never logged in / blank lastLoginTime): ${skippedNeverLoggedIn}`);
256
+ console.log(` ${inactiveItems.length} of ${usersWithLogin.length} users with login history are inactive`);
257
+ console.log("-------------------------------------");
258
+ console.log("Collecting all unique properties...");
259
+ // Collect union of all properties (from inactive users only)
260
+ const allProperties = new Set();
261
+ for (const item of inactiveItems) {
262
+ for (const key of Object.keys(item)) {
263
+ if (!["resources", "collaborationGroups", "resourceInternalIds", "links"].includes(key)) {
264
+ allProperties.add(key);
265
+ }
266
+ }
267
+ }
268
+ const headers = Array.from(allProperties);
269
+ console.log(`Total unique fields: ${headers.length}`);
270
+ // Build array of plain objects (one per inactive user)
271
+ const result = inactiveItems.map(item => {
272
+ const obj = {};
273
+ for (const field of headers) {
274
+ let value = item[field];
275
+ if (field === "keys") {
276
+ obj[field] = Array.isArray(value) ? value.join("|") : (value ?? "");
277
+ continue;
278
+ }
279
+ // Keep objects as objects (no JSON-stringify/escaping needed since this isn't CSV)
280
+ obj[field] = value !== undefined ? value : null;
281
+ }
282
+ return obj;
283
+ });
284
+ console.log("-------------------------------------");
285
+ console.log("Inactive Users Collected Successfully!");
286
+ console.log(`Total Fetched: ${totalFetched}`);
287
+ console.log(`Skipped (never logged in): ${skippedNeverLoggedIn}`);
288
+ console.log(`Inactive Records (>${inactivityThresholdDays} days): ${result.length}`);
289
+ console.log(`Total Columns (Dynamic): ${headers.length}`);
290
+ console.log(`Date Time: ${new Date()}`);
291
+ console.log("-------------------------------------");
292
+ return result;
293
+ }
123
294
  exports.default = OfscUserUtility;
@@ -30,4 +30,9 @@ interface ValidationResult {
30
30
  export declare function validateDateTimeStrict(dateTimeStr: string): ValidationResult;
31
31
  export declare function getTimeBefore180SecondsAlt(): string;
32
32
  export declare function getTimeBefore3600SecondsAlt(): string;
33
+ /**
34
+ * Parses OFSC-style "YYYY-MM-DD HH:mm:ss" into a Date object.
35
+ * Falls back to null if the value is missing/unparseable.
36
+ */
37
+ export declare function parseOFSCDate(value: unknown): Date | null;
33
38
  export {};
@@ -43,6 +43,7 @@ exports.createExcelFile = createExcelFile;
43
43
  exports.validateDateTimeStrict = validateDateTimeStrict;
44
44
  exports.getTimeBefore180SecondsAlt = getTimeBefore180SecondsAlt;
45
45
  exports.getTimeBefore3600SecondsAlt = getTimeBefore3600SecondsAlt;
46
+ exports.parseOFSCDate = parseOFSCDate;
46
47
  const XLSX = __importStar(require("xlsx-js-style"));
47
48
  const xmldom_1 = require("xmldom");
48
49
  const oauthTokenService_1 = require("../oauthTokenService");
@@ -318,3 +319,15 @@ function getTimeBefore3600SecondsAlt() {
318
319
  .replace('T', ' ')
319
320
  .substring(0, 19);
320
321
  }
322
+ /**
323
+ * Parses OFSC-style "YYYY-MM-DD HH:mm:ss" into a Date object.
324
+ * Falls back to null if the value is missing/unparseable.
325
+ */
326
+ function parseOFSCDate(value) {
327
+ if (!value || typeof value !== "string")
328
+ return null;
329
+ // Convert "2026-08-07 12:31:18" -> "2026-08-07T12:31:18"
330
+ const isoLike = value.trim().replace(" ", "T");
331
+ const d = new Date(isoLike);
332
+ return isNaN(d.getTime()) ? null : d;
333
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ofsc-utility",
3
- "version": "1.0.37",
3
+ "version": "1.0.39",
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,163 @@ 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
+ });
147
196
  ```
148
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
241
+ ```
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(never loggedin)
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
+
149
304
  ### Resource related methods
150
305
 
151
306
  ```js
@@ -285,6 +440,7 @@ Top-level exports include:
285
440
  - `downloadWorkZoneCSV`
286
441
  - `downloadAllResourcesCSV`
287
442
  - `downloadAllUsersCSV`
443
+ - `downloadAllInactiveUsersCSV`
288
444
  - `generateAllOnHandInventoryOfAllResourcesCSV`
289
445
  - `generateAllOnHandInventoryOfAllResources`
290
446
  - `downloadAllInventoryTypesCSV`