shuttlepro-shared 1.4.14 → 1.4.16

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.
@@ -35,13 +35,21 @@ class CallRepository {
35
35
  }
36
36
  }
37
37
 
38
+ async getCall(callId) {
39
+ try {
40
+ return await Call.findOne({ callId }).lean().exec();
41
+ } catch (error) {
42
+ console.error(`❌ Failed to end call ${callId}:`, error);
43
+ return null;
44
+ }
45
+ }
46
+
38
47
  async endCall(callId, agentId = null, updateObj = {}, callHistory = {}) {
39
48
  try {
40
49
  return await Call.findOneAndUpdate(
41
50
  { callId },
42
51
  {
43
52
  $set: {
44
- ...(updateObj?.status === "ended" ? { endTime: new Date() } : {}),
45
53
  ...updateObj,
46
54
  },
47
55
  $push: {
@@ -193,18 +201,54 @@ class CallRepository {
193
201
 
194
202
  /* ----------------- Query Helpers ----------------- */
195
203
 
196
- async getCallsByWorkspace(workspaceId, filters = {}) {
197
- return Call.find({ workspaceId, ...filters })
198
- .sort({ createdAt: -1 })
199
- .lean()
200
- .exec();
204
+ async getCallsByWorkspace(
205
+ workspaceId,
206
+ {
207
+ filters = {},
208
+ select = null,
209
+ sortBy = { createdAt: -1 },
210
+ limit = null,
211
+ skip = null,
212
+ } = {}
213
+ ) {
214
+ let query = Call.find({ workspaceId, ...filters })
215
+ .select(select || {}) // if null → select all fields
216
+ .sort(sortBy) // default newest first
217
+ .lean();
218
+
219
+ if (limit !== null) {
220
+ query = query.limit(limit);
221
+ }
222
+ if (skip !== null) {
223
+ query = query.skip(skip);
224
+ }
225
+
226
+ return query.exec();
201
227
  }
202
228
 
203
- async getCallsByAgent(agentId, filters = {}) {
204
- return Call.find({ agentId, ...filters })
205
- .sort({ createdAt: -1 })
206
- .lean()
207
- .exec();
229
+ async getCallsByAgent(
230
+ agentId,
231
+ {
232
+ filters = {},
233
+ select = null,
234
+ sortBy = { createdAt: -1 },
235
+ limit = null,
236
+ skip = null,
237
+ } = {}
238
+ ) {
239
+ let query = Call.find({ agentId, ...filters })
240
+ .select(select || {}) // if null → select all fields
241
+ .sort(sortBy) // default newest first
242
+ .lean();
243
+
244
+ if (limit !== null) {
245
+ query = query.limit(limit);
246
+ }
247
+ if (skip !== null) {
248
+ query = query.skip(skip);
249
+ }
250
+
251
+ return query.exec();
208
252
  }
209
253
 
210
254
  async getCallsByReceiver(receiver, filters = {}) {
@@ -236,45 +280,109 @@ class CallRepository {
236
280
  /* ----------------- Reporting & Analytics ----------------- */
237
281
 
238
282
  // Stats grouped by agent
239
- async getAgentStats(workspaceId, from, to) {
240
- return Call.aggregate([
241
- { $match: { workspaceId, createdAt: { $gte: from, $lte: to } } },
283
+ // Stats grouped by agent
284
+ async getAgentStats({ from, to }, filters = {}) {
285
+ const result = await Call.aggregate([
286
+ {
287
+ $match: {
288
+ createdAt: { $gte: from, $lte: to },
289
+ ...filters,
290
+ },
291
+ },
242
292
  {
243
293
  $group: {
244
294
  _id: "$agentId",
245
295
  totalCalls: { $sum: 1 },
246
- answered: {
247
- $sum: { $cond: [{ $eq: ["$status", "active"] }, 1, 0] },
296
+ pending: {
297
+ $sum: { $cond: [{ $eq: ["$status", "pending"] }, 1, 0] },
298
+ },
299
+ abandoned: {
300
+ $sum: { $cond: [{ $eq: ["$status", "abandoned"] }, 1, 0] },
248
301
  },
249
302
  ended: {
250
303
  $sum: { $cond: [{ $eq: ["$status", "ended"] }, 1, 0] },
251
304
  },
252
- avgDuration: { $avg: "$duration" },
305
+ },
306
+ },
307
+ {
308
+ $project: {
309
+ _id: 0,
310
+ agentId: "$_id",
311
+ totalCalls: 1,
312
+ pending: 1,
313
+ abandoned: 1,
314
+ ended: 1,
253
315
  },
254
316
  },
255
317
  ]);
318
+
319
+ if (filters.agentId) {
320
+ // single agent case
321
+ return (
322
+ result.find((r) => r.agentId === filters.agentId) || {
323
+ agentId: filters.agentId,
324
+ totalCalls: 0,
325
+ pending: 0,
326
+ abandoned: 0,
327
+ ended: 0,
328
+ }
329
+ );
330
+ }
331
+
332
+ // multi-agent case
333
+ if (result.length === 0) {
334
+ return [];
335
+ }
336
+
337
+ return result;
256
338
  }
257
339
 
258
340
  // Daily call summary
259
- async getDailySummary(workspaceId, from, to) {
341
+ async getDailySummary(workspaceId, from, to, filters = {}, groupBy = null) {
342
+ const matchStage = {
343
+ workspaceId,
344
+ createdAt: { $gte: from, $lte: to },
345
+ ...filters,
346
+ };
347
+
348
+ const groupId = {
349
+ day: { $dateToString: { format: "%Y-%m-%d", date: "$createdAt" } },
350
+ };
351
+
352
+ // If admin wants to group by agentId / receiver / platformId
353
+ if (groupBy && ["agentId", "receiver", "platformId"].includes(groupBy)) {
354
+ groupId[groupBy] = `$${groupBy}`;
355
+ }
356
+
260
357
  return Call.aggregate([
261
- { $match: { workspaceId, createdAt: { $gte: from, $lte: to } } },
358
+ { $match: matchStage },
262
359
  {
263
360
  $group: {
264
- _id: {
265
- day: { $dateToString: { format: "%Y-%m-%d", date: "$createdAt" } },
266
- },
361
+ _id: groupId,
267
362
  total: { $sum: 1 },
268
- ended: {
269
- $sum: { $cond: [{ $eq: ["$status", "ended"] }, 1, 0] },
270
- },
363
+ ended: { $sum: { $cond: [{ $eq: ["$status", "ended"] }, 1, 0] } },
271
364
  abandoned: {
272
365
  $sum: { $cond: [{ $eq: ["$status", "abandoned"] }, 1, 0] },
273
366
  },
274
367
  avgDuration: { $avg: "$duration" },
368
+ minDuration: { $min: "$duration" },
369
+ maxDuration: { $max: "$duration" },
370
+ },
371
+ },
372
+ {
373
+ $project: {
374
+ _id: 0,
375
+ day: "$_id.day",
376
+ groupBy: groupBy ? `$_id.${groupBy}` : null,
377
+ total: 1,
378
+ ended: 1,
379
+ abandoned: 1,
380
+ avgDuration: { $round: ["$avgDuration", 2] }, // round to 2 decimals
381
+ minDuration: 1,
382
+ maxDuration: 1,
275
383
  },
276
384
  },
277
- { $sort: { "_id.day": 1 } },
385
+ { $sort: { day: 1 } },
278
386
  ]);
279
387
  }
280
388
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shuttlepro-shared",
3
- "version": "1.4.14",
3
+ "version": "1.4.16",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {