@talkpilot/core-db 1.3.70 → 1.3.72
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/README.md +20 -0
- package/dist/municipal/muniIssues/index.d.ts +2 -2
- package/dist/municipal/muniIssues/index.d.ts.map +1 -1
- package/dist/municipal/muniIssues/index.js +2 -1
- package/dist/municipal/muniIssues/index.js.map +1 -1
- package/dist/municipal/muniIssues/muniIssues.constants.d.ts +2 -0
- package/dist/municipal/muniIssues/muniIssues.constants.d.ts.map +1 -1
- package/dist/municipal/muniIssues/muniIssues.constants.js +8 -1
- package/dist/municipal/muniIssues/muniIssues.constants.js.map +1 -1
- package/dist/municipal/muniIssues/muniIssues.getters.d.ts +6 -1
- package/dist/municipal/muniIssues/muniIssues.getters.d.ts.map +1 -1
- package/dist/municipal/muniIssues/muniIssues.getters.js +31 -1
- package/dist/municipal/muniIssues/muniIssues.getters.js.map +1 -1
- package/dist/municipal/muniIssues/muniIssues.schema.d.ts.map +1 -1
- package/dist/municipal/muniIssues/muniIssues.schema.js +13 -0
- package/dist/municipal/muniIssues/muniIssues.schema.js.map +1 -1
- package/dist/municipal/muniIssues/muniIssues.setters.d.ts +5 -1
- package/dist/municipal/muniIssues/muniIssues.setters.d.ts.map +1 -1
- package/dist/municipal/muniIssues/muniIssues.setters.js +39 -2
- package/dist/municipal/muniIssues/muniIssues.setters.js.map +1 -1
- package/dist/municipal/muniIssues/muniIssues.types.d.ts +23 -2
- package/dist/municipal/muniIssues/muniIssues.types.d.ts.map +1 -1
- package/dist/talkpilot/calls/calls.constants.d.ts +6 -0
- package/dist/talkpilot/calls/calls.constants.d.ts.map +1 -1
- package/dist/talkpilot/calls/calls.constants.js +7 -1
- package/dist/talkpilot/calls/calls.constants.js.map +1 -1
- package/dist/talkpilot/calls/calls.types.d.ts +2 -2
- package/dist/talkpilot/calls/calls.types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/municipal/muniIssues/__tests__/muniIssues.getters.spec.ts +60 -0
- package/src/municipal/muniIssues/__tests__/muniIssues.setters.spec.ts +74 -1
- package/src/municipal/muniIssues/__tests__/muniIssues.setup.spec.ts +1 -0
- package/src/municipal/muniIssues/index.ts +5 -0
- package/src/municipal/muniIssues/muniIssues.constants.ts +8 -0
- package/src/municipal/muniIssues/muniIssues.getters.ts +46 -2
- package/src/municipal/muniIssues/muniIssues.schema.ts +14 -0
- package/src/municipal/muniIssues/muniIssues.setters.ts +59 -3
- package/src/municipal/muniIssues/muniIssues.types.ts +34 -4
- package/src/talkpilot/calls/__tests__/calls.spec.ts +7 -7
- package/src/talkpilot/calls/calls.constants.ts +7 -0
- package/src/talkpilot/calls/calls.types.ts +2 -1
package/README.md
CHANGED
|
@@ -182,6 +182,8 @@ Refer to `DEVELOPMENT.md` for the full walkthrough, token instructions, and fact
|
|
|
182
182
|
| 1.3.61 | Optional `activeFlowId` on `WebsiteTalkProduct` (CP-1849). WebsiteTalk updates this flow instead of the flow on a phone number. |
|
|
183
183
|
| 1.3.62 | Pause/Resume support for WebsiteTalk scans (CP-1752): `PAUSED` and `CANCELED` added to `SCAN_STATUSES` (`PAUSED` deliberately excluded from `FINISHED_SCAN_STATUSES` — pausing isn't done, `CANCELED` is). Added `Scan.pausedAt`, tracked across pause/resume so elapsed-time display can freeze correctly instead of counting the paused duration. |
|
|
184
184
|
| 1.3.64 | New `callTokenUsage` collection with `incrementCallTokenUsage` (CP-1810): per-call realtime token usage, one document per `callSid`. |
|
|
185
|
+
| 1.3.71 | Who ended a call (CP-1873): optional `Call.endedBy` (`system` / `agent` / `user`), `CallEndedByValues`, and `setCallEndedBy` (first write wins). **Breaking** vs 1.3.69–1.3.70: the value `resident` was replaced by `user`. |
|
|
186
|
+
| 1.3.72 | Complaint tracking on `muniIssues` (CP-1888): optional `status` / `response` / `updatedAt`, `updateMuniIssueStatus`, `setMuniIssueResponse` (one response, then locked), `findMuniIssuesByClient`, `countMuniIssuesByClient`, `getMuniIssueByIdForClient`, index `clientId_1_createdAt_-1`. |
|
|
185
187
|
|
|
186
188
|
### 1.1.9
|
|
187
189
|
|
|
@@ -804,6 +806,24 @@ Optional `activeFlowId` on `WebsiteTalkProduct`. WTIS reads this field when it u
|
|
|
804
806
|
|
|
805
807
|
No — the field is optional. **WTIS** should upgrade to **1.3.61** so the call-script update can use it.
|
|
806
808
|
|
|
809
|
+
### 1.3.71 — Who ended the call (CP-1873)
|
|
810
|
+
|
|
811
|
+
Records who ended a connected call. Unset for calls that never connected (busy, no-answer) and for calls moved to a conference.
|
|
812
|
+
|
|
813
|
+
**Added**
|
|
814
|
+
|
|
815
|
+
| Item | Purpose |
|
|
816
|
+
| --- | --- |
|
|
817
|
+
| `Call.endedBy` | `system` (our platform, e.g. silence timeout), `agent` (the bot ended the flow) or `user` (the caller hung up). |
|
|
818
|
+
| `CallEndedByValues` / `CallEndedBy` | Const object and the type derived from it, same pattern as `CallStatusValues`. |
|
|
819
|
+
| `setCallEndedBy(callSid, endedBy)` | Writes only if `endedBy` is not set yet; returns `true` when this call wrote it. |
|
|
820
|
+
|
|
821
|
+
`endedBy` is not part of `CallUpdateParams`, so `updateCallByCallSid` cannot overwrite it.
|
|
822
|
+
|
|
823
|
+
**Do I need to update my app?**
|
|
824
|
+
|
|
825
|
+
**TalkPilot Server** must upgrade to **1.3.71** to write the field. 1.3.69–1.3.70 used `resident` instead of `user` — don't use them.
|
|
826
|
+
|
|
807
827
|
## 🛠 CI/CD & deployment
|
|
808
828
|
|
|
809
829
|
- This package is consumed by Cloud Build-based services (TalkPilot Server, MIS, CIS) as a dependency when Docker images are built. Keep `dist/` in sync with your builds because the compiled artifact is what downstream services install.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export * from "./muniIssues.getters";
|
|
2
2
|
export * from "./muniIssues.setters";
|
|
3
3
|
export * from "./muniIssues.setup";
|
|
4
|
-
export { MUNI_ISSUES_COLLECTION, MUNI_ISSUE_SYNC_STATUSES, MUNI_ISSUE_TEXT_MAX_LENGTH, MUNI_ISSUE_NAME_MAX_LENGTH, } from "./muniIssues.constants";
|
|
4
|
+
export { MUNI_ISSUES_COLLECTION, MUNI_ISSUE_STATUSES, MUNI_ISSUE_SYNC_STATUSES, MUNI_ISSUE_TEXT_MAX_LENGTH, MUNI_ISSUE_NAME_MAX_LENGTH, } from "./muniIssues.constants";
|
|
5
5
|
export { muniIssuesJsonSchema, muniIssuesIndexes } from "./muniIssues.schema";
|
|
6
|
-
export type { MuniIssue, MuniIssueContent, MuniIssueJira, MuniIssueSyncStatus, CreateMuniIssueInput, } from "./muniIssues.types";
|
|
6
|
+
export type { MuniIssue, MuniIssueContent, MuniIssueJira, MuniIssueSyncStatus, MuniIssueStatus, MuniIssueResponse, CreateMuniIssueInput, SetMuniIssueResponseInput, FindMuniIssuesOptions, } from "./muniIssues.types";
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/municipal/muniIssues/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,OAAO,EACL,sBAAsB,EACtB,wBAAwB,EACxB,0BAA0B,EAC1B,0BAA0B,GAC3B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,YAAY,EACV,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,mBAAmB,EACnB,oBAAoB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/municipal/muniIssues/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,wBAAwB,EACxB,0BAA0B,EAC1B,0BAA0B,GAC3B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,YAAY,EACV,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,oBAAoB,EACpB,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,oBAAoB,CAAC"}
|
|
@@ -14,12 +14,13 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.muniIssuesIndexes = exports.muniIssuesJsonSchema = exports.MUNI_ISSUE_NAME_MAX_LENGTH = exports.MUNI_ISSUE_TEXT_MAX_LENGTH = exports.MUNI_ISSUE_SYNC_STATUSES = exports.MUNI_ISSUES_COLLECTION = void 0;
|
|
17
|
+
exports.muniIssuesIndexes = exports.muniIssuesJsonSchema = exports.MUNI_ISSUE_NAME_MAX_LENGTH = exports.MUNI_ISSUE_TEXT_MAX_LENGTH = exports.MUNI_ISSUE_SYNC_STATUSES = exports.MUNI_ISSUE_STATUSES = exports.MUNI_ISSUES_COLLECTION = void 0;
|
|
18
18
|
__exportStar(require("./muniIssues.getters"), exports);
|
|
19
19
|
__exportStar(require("./muniIssues.setters"), exports);
|
|
20
20
|
__exportStar(require("./muniIssues.setup"), exports);
|
|
21
21
|
var muniIssues_constants_1 = require("./muniIssues.constants");
|
|
22
22
|
Object.defineProperty(exports, "MUNI_ISSUES_COLLECTION", { enumerable: true, get: function () { return muniIssues_constants_1.MUNI_ISSUES_COLLECTION; } });
|
|
23
|
+
Object.defineProperty(exports, "MUNI_ISSUE_STATUSES", { enumerable: true, get: function () { return muniIssues_constants_1.MUNI_ISSUE_STATUSES; } });
|
|
23
24
|
Object.defineProperty(exports, "MUNI_ISSUE_SYNC_STATUSES", { enumerable: true, get: function () { return muniIssues_constants_1.MUNI_ISSUE_SYNC_STATUSES; } });
|
|
24
25
|
Object.defineProperty(exports, "MUNI_ISSUE_TEXT_MAX_LENGTH", { enumerable: true, get: function () { return muniIssues_constants_1.MUNI_ISSUE_TEXT_MAX_LENGTH; } });
|
|
25
26
|
Object.defineProperty(exports, "MUNI_ISSUE_NAME_MAX_LENGTH", { enumerable: true, get: function () { return muniIssues_constants_1.MUNI_ISSUE_NAME_MAX_LENGTH; } });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/municipal/muniIssues/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,uDAAqC;AACrC,uDAAqC;AACrC,qDAAmC;AACnC,+
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/municipal/muniIssues/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,uDAAqC;AACrC,uDAAqC;AACrC,qDAAmC;AACnC,+DAMgC;AAL9B,8HAAA,sBAAsB,OAAA;AACtB,2HAAA,mBAAmB,OAAA;AACnB,gIAAA,wBAAwB,OAAA;AACxB,kIAAA,0BAA0B,OAAA;AAC1B,kIAAA,0BAA0B,OAAA;AAE5B,yDAA8E;AAArE,yHAAA,oBAAoB,OAAA;AAAE,sHAAA,iBAAiB,OAAA"}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
export declare const MUNI_ISSUES_COLLECTION = "muniIssues";
|
|
3
3
|
/** Sync state of the issue's Jira ticket at write time. */
|
|
4
4
|
export declare const MUNI_ISSUE_SYNC_STATUSES: readonly ["created", "failed"];
|
|
5
|
+
/** Customer-facing complaint status, set manually by the TalkPilot team. */
|
|
6
|
+
export declare const MUNI_ISSUE_STATUSES: readonly ["in_review", "in_progress", "resolved", "no_issue_found"];
|
|
5
7
|
/** Max length for the long free-text fields — well above the ~2000-char target. */
|
|
6
8
|
export declare const MUNI_ISSUE_TEXT_MAX_LENGTH = 5000;
|
|
7
9
|
/** Max length for the short identity fields (a person's name and role). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"muniIssues.constants.d.ts","sourceRoot":"","sources":["../../../src/municipal/muniIssues/muniIssues.constants.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,eAAO,MAAM,sBAAsB,eAAe,CAAC;AAEnD,2DAA2D;AAC3D,eAAO,MAAM,wBAAwB,gCAAiC,CAAC;AAEvE,mFAAmF;AACnF,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,2EAA2E;AAC3E,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C,uGAAuG;AACvG,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,yEAAyE;AACzE,eAAO,MAAM,gBAAgB,EAAG,QAAiB,CAAC;AAClD,eAAO,MAAM,iBAAiB,EAAG,OAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"muniIssues.constants.d.ts","sourceRoot":"","sources":["../../../src/municipal/muniIssues/muniIssues.constants.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,eAAO,MAAM,sBAAsB,eAAe,CAAC;AAEnD,2DAA2D;AAC3D,eAAO,MAAM,wBAAwB,gCAAiC,CAAC;AAEvE,4EAA4E;AAC5E,eAAO,MAAM,mBAAmB,qEAKtB,CAAC;AAEX,mFAAmF;AACnF,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,2EAA2E;AAC3E,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C,uGAAuG;AACvG,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,yEAAyE;AACzE,eAAO,MAAM,gBAAgB,EAAG,QAAiB,CAAC;AAClD,eAAO,MAAM,iBAAiB,EAAG,OAAgB,CAAC"}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.VALIDATION_ACTION = exports.VALIDATION_LEVEL = exports.NAMESPACE_EXISTS = exports.MUNI_ISSUE_NAME_MAX_LENGTH = exports.MUNI_ISSUE_TEXT_MAX_LENGTH = exports.MUNI_ISSUE_SYNC_STATUSES = exports.MUNI_ISSUES_COLLECTION = void 0;
|
|
3
|
+
exports.VALIDATION_ACTION = exports.VALIDATION_LEVEL = exports.NAMESPACE_EXISTS = exports.MUNI_ISSUE_NAME_MAX_LENGTH = exports.MUNI_ISSUE_TEXT_MAX_LENGTH = exports.MUNI_ISSUE_STATUSES = exports.MUNI_ISSUE_SYNC_STATUSES = exports.MUNI_ISSUES_COLLECTION = void 0;
|
|
4
4
|
/** Mongo collection name for reported municipal issues. */
|
|
5
5
|
exports.MUNI_ISSUES_COLLECTION = "muniIssues";
|
|
6
6
|
/** Sync state of the issue's Jira ticket at write time. */
|
|
7
7
|
exports.MUNI_ISSUE_SYNC_STATUSES = ["created", "failed"];
|
|
8
|
+
/** Customer-facing complaint status, set manually by the TalkPilot team. */
|
|
9
|
+
exports.MUNI_ISSUE_STATUSES = [
|
|
10
|
+
"in_review",
|
|
11
|
+
"in_progress",
|
|
12
|
+
"resolved",
|
|
13
|
+
"no_issue_found",
|
|
14
|
+
];
|
|
8
15
|
/** Max length for the long free-text fields — well above the ~2000-char target. */
|
|
9
16
|
exports.MUNI_ISSUE_TEXT_MAX_LENGTH = 5000;
|
|
10
17
|
/** Max length for the short identity fields (a person's name and role). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"muniIssues.constants.js","sourceRoot":"","sources":["../../../src/municipal/muniIssues/muniIssues.constants.ts"],"names":[],"mappings":";;;AAAA,2DAA2D;AAC9C,QAAA,sBAAsB,GAAG,YAAY,CAAC;AAEnD,2DAA2D;AAC9C,QAAA,wBAAwB,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAU,CAAC;AAEvE,mFAAmF;AACtE,QAAA,0BAA0B,GAAG,IAAI,CAAC;AAE/C,2EAA2E;AAC9D,QAAA,0BAA0B,GAAG,GAAG,CAAC;AAE9C,uGAAuG;AAC1F,QAAA,gBAAgB,GAAG,EAAE,CAAC;AAEnC,yEAAyE;AAC5D,QAAA,gBAAgB,GAAG,QAAiB,CAAC;AACrC,QAAA,iBAAiB,GAAG,OAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"muniIssues.constants.js","sourceRoot":"","sources":["../../../src/municipal/muniIssues/muniIssues.constants.ts"],"names":[],"mappings":";;;AAAA,2DAA2D;AAC9C,QAAA,sBAAsB,GAAG,YAAY,CAAC;AAEnD,2DAA2D;AAC9C,QAAA,wBAAwB,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAU,CAAC;AAEvE,4EAA4E;AAC/D,QAAA,mBAAmB,GAAG;IACjC,WAAW;IACX,aAAa;IACb,UAAU;IACV,gBAAgB;CACR,CAAC;AAEX,mFAAmF;AACtE,QAAA,0BAA0B,GAAG,IAAI,CAAC;AAE/C,2EAA2E;AAC9D,QAAA,0BAA0B,GAAG,GAAG,CAAC;AAE9C,uGAAuG;AAC1F,QAAA,gBAAgB,GAAG,EAAE,CAAC;AAEnC,yEAAyE;AAC5D,QAAA,gBAAgB,GAAG,QAAiB,CAAC;AACrC,QAAA,iBAAiB,GAAG,OAAgB,CAAC"}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { Collection, ObjectId } from "mongodb";
|
|
2
|
-
import { MuniIssue } from "./muniIssues.types";
|
|
2
|
+
import { FindMuniIssuesOptions, MuniIssue } from "./muniIssues.types";
|
|
3
3
|
export declare const getMuniIssuesCollection: () => Collection<MuniIssue>;
|
|
4
4
|
export declare const generateMuniIssueId: () => ObjectId;
|
|
5
5
|
export declare const getMuniIssueById: (id: string) => Promise<MuniIssue | null>;
|
|
6
6
|
export declare const getMuniIssueByJiraKey: (jiraKey: string) => Promise<MuniIssue | null>;
|
|
7
|
+
/** A client's tracked issues, newest first. */
|
|
8
|
+
export declare const findMuniIssuesByClient: (clientId: string, { answered, skip, limit }?: FindMuniIssuesOptions) => Promise<MuniIssue[]>;
|
|
9
|
+
export declare const countMuniIssuesByClient: (clientId: string, { answered }?: Pick<FindMuniIssuesOptions, "answered">) => Promise<number>;
|
|
10
|
+
/** One tracked issue, only if it belongs to the client. */
|
|
11
|
+
export declare const getMuniIssueByIdForClient: (id: string, clientId: string) => Promise<MuniIssue | null>;
|
|
7
12
|
//# sourceMappingURL=muniIssues.getters.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"muniIssues.getters.d.ts","sourceRoot":"","sources":["../../../src/municipal/muniIssues/muniIssues.getters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"muniIssues.getters.d.ts","sourceRoot":"","sources":["../../../src/municipal/muniIssues/muniIssues.getters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAU,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEvD,OAAO,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAGtE,eAAO,MAAM,uBAAuB,QAAO,UAAU,CAAC,SAAS,CAE9D,CAAC;AAEF,eAAO,MAAM,mBAAmB,QAAO,QAA0B,CAAC;AAElE,eAAO,MAAM,gBAAgB,GAC3B,IAAI,MAAM,KACT,OAAO,CAAC,SAAS,GAAG,IAAI,CAI1B,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,SAAS,MAAM,KACd,OAAO,CAAC,SAAS,GAAG,IAAI,CAE1B,CAAC;AAYF,+CAA+C;AAC/C,eAAO,MAAM,sBAAsB,GACjC,UAAU,MAAM,EAChB,4BAAmC,qBAA0B,KAC5D,OAAO,CAAC,SAAS,EAAE,CAOrB,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,UAAU,MAAM,EAChB,eAAc,IAAI,CAAC,qBAAqB,EAAE,UAAU,CAAM,KACzD,OAAO,CAAC,MAAM,CAIhB,CAAC;AAEF,2DAA2D;AAC3D,eAAO,MAAM,yBAAyB,GACpC,IAAI,MAAM,EACV,UAAU,MAAM,KACf,OAAO,CAAC,SAAS,GAAG,IAAI,CAM1B,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getMuniIssueByJiraKey = exports.getMuniIssueById = exports.generateMuniIssueId = exports.getMuniIssuesCollection = void 0;
|
|
3
|
+
exports.getMuniIssueByIdForClient = exports.countMuniIssuesByClient = exports.findMuniIssuesByClient = exports.getMuniIssueByJiraKey = exports.getMuniIssueById = exports.generateMuniIssueId = exports.getMuniIssuesCollection = void 0;
|
|
4
4
|
const mongodb_1 = require("mongodb");
|
|
5
5
|
const index_1 = require("../index");
|
|
6
6
|
const muniIssues_constants_1 = require("./muniIssues.constants");
|
|
@@ -21,4 +21,34 @@ const getMuniIssueByJiraKey = async (jiraKey) => {
|
|
|
21
21
|
return (0, exports.getMuniIssuesCollection)().findOne({ "jira.key": jiraKey });
|
|
22
22
|
};
|
|
23
23
|
exports.getMuniIssueByJiraKey = getMuniIssueByJiraKey;
|
|
24
|
+
// Tracked issues of one client. Issues without a status predate complaint tracking and are excluded.
|
|
25
|
+
const clientIssuesFilter = (clientId, answered) => ({
|
|
26
|
+
clientId,
|
|
27
|
+
status: { $exists: true },
|
|
28
|
+
...(answered !== undefined && { response: { $exists: answered } }),
|
|
29
|
+
});
|
|
30
|
+
/** A client's tracked issues, newest first. */
|
|
31
|
+
const findMuniIssuesByClient = async (clientId, { answered, skip = 0, limit = 0 } = {}) => {
|
|
32
|
+
return (0, exports.getMuniIssuesCollection)()
|
|
33
|
+
.find(clientIssuesFilter(clientId, answered))
|
|
34
|
+
.sort({ createdAt: -1 })
|
|
35
|
+
.skip(skip)
|
|
36
|
+
.limit(limit)
|
|
37
|
+
.toArray();
|
|
38
|
+
};
|
|
39
|
+
exports.findMuniIssuesByClient = findMuniIssuesByClient;
|
|
40
|
+
const countMuniIssuesByClient = async (clientId, { answered } = {}) => {
|
|
41
|
+
return (0, exports.getMuniIssuesCollection)().countDocuments(clientIssuesFilter(clientId, answered));
|
|
42
|
+
};
|
|
43
|
+
exports.countMuniIssuesByClient = countMuniIssuesByClient;
|
|
44
|
+
/** One tracked issue, only if it belongs to the client. */
|
|
45
|
+
const getMuniIssueByIdForClient = async (id, clientId) => {
|
|
46
|
+
if (!mongodb_1.ObjectId.isValid(id))
|
|
47
|
+
return null;
|
|
48
|
+
return (0, exports.getMuniIssuesCollection)().findOne({
|
|
49
|
+
...clientIssuesFilter(clientId),
|
|
50
|
+
_id: new mongodb_1.ObjectId(id),
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
exports.getMuniIssueByIdForClient = getMuniIssueByIdForClient;
|
|
24
54
|
//# sourceMappingURL=muniIssues.getters.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"muniIssues.getters.js","sourceRoot":"","sources":["../../../src/municipal/muniIssues/muniIssues.getters.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"muniIssues.getters.js","sourceRoot":"","sources":["../../../src/municipal/muniIssues/muniIssues.getters.ts"],"names":[],"mappings":";;;AAAA,qCAAuD;AACvD,oCAAiC;AAEjC,iEAAgE;AAEzD,MAAM,uBAAuB,GAAG,GAA0B,EAAE;IACjE,OAAO,IAAA,aAAK,GAAE,CAAC,UAAU,CAAY,6CAAsB,CAAC,CAAC;AAC/D,CAAC,CAAC;AAFW,QAAA,uBAAuB,2BAElC;AAEK,MAAM,mBAAmB,GAAG,GAAa,EAAE,CAAC,IAAI,kBAAQ,EAAE,CAAC;AAArD,QAAA,mBAAmB,uBAAkC;AAE3D,MAAM,gBAAgB,GAAG,KAAK,EACnC,EAAU,EACiB,EAAE;IAC7B,iFAAiF;IACjF,IAAI,CAAC,kBAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,IAAA,+BAAuB,GAAE,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,kBAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;AACtE,CAAC,CAAC;AANW,QAAA,gBAAgB,oBAM3B;AAEK,MAAM,qBAAqB,GAAG,KAAK,EACxC,OAAe,EACY,EAAE;IAC7B,OAAO,IAAA,+BAAuB,GAAE,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;AACpE,CAAC,CAAC;AAJW,QAAA,qBAAqB,yBAIhC;AAEF,qGAAqG;AACrG,MAAM,kBAAkB,GAAG,CACzB,QAAgB,EAChB,QAAkB,EACC,EAAE,CAAC,CAAC;IACvB,QAAQ;IACR,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;IACzB,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC;CACnE,CAAC,CAAC;AAEH,+CAA+C;AACxC,MAAM,sBAAsB,GAAG,KAAK,EACzC,QAAgB,EAChB,EAAE,QAAQ,EAAE,IAAI,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,KAA4B,EAAE,EACvC,EAAE;IACxB,OAAO,IAAA,+BAAuB,GAAE;SAC7B,IAAI,CAAC,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;SAC5C,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC;SACvB,IAAI,CAAC,IAAI,CAAC;SACV,KAAK,CAAC,KAAK,CAAC;SACZ,OAAO,EAAE,CAAC;AACf,CAAC,CAAC;AAVW,QAAA,sBAAsB,0BAUjC;AAEK,MAAM,uBAAuB,GAAG,KAAK,EAC1C,QAAgB,EAChB,EAAE,QAAQ,KAA8C,EAAE,EACzC,EAAE;IACnB,OAAO,IAAA,+BAAuB,GAAE,CAAC,cAAc,CAC7C,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CACvC,CAAC;AACJ,CAAC,CAAC;AAPW,QAAA,uBAAuB,2BAOlC;AAEF,2DAA2D;AACpD,MAAM,yBAAyB,GAAG,KAAK,EAC5C,EAAU,EACV,QAAgB,EACW,EAAE;IAC7B,IAAI,CAAC,kBAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,IAAA,+BAAuB,GAAE,CAAC,OAAO,CAAC;QACvC,GAAG,kBAAkB,CAAC,QAAQ,CAAC;QAC/B,GAAG,EAAE,IAAI,kBAAQ,CAAC,EAAE,CAAC;KACtB,CAAC,CAAC;AACL,CAAC,CAAC;AATW,QAAA,yBAAyB,6BASpC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"muniIssues.schema.d.ts","sourceRoot":"","sources":["../../../src/municipal/muniIssues/muniIssues.schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"muniIssues.schema.d.ts","sourceRoot":"","sources":["../../../src/municipal/muniIssues/muniIssues.schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAQ1D,eAAO,MAAM,oBAAoB,EAAE,QA8DlC,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,gBAAgB,EAO/C,CAAC"}
|
|
@@ -52,11 +52,24 @@ exports.muniIssuesJsonSchema = {
|
|
|
52
52
|
},
|
|
53
53
|
},
|
|
54
54
|
createdAt: { bsonType: "date" },
|
|
55
|
+
status: { enum: [...muniIssues_constants_1.MUNI_ISSUE_STATUSES] },
|
|
56
|
+
response: {
|
|
57
|
+
bsonType: "object",
|
|
58
|
+
required: ["text", "createdAt"],
|
|
59
|
+
properties: {
|
|
60
|
+
text: { bsonType: "string", maxLength: muniIssues_constants_1.MUNI_ISSUE_TEXT_MAX_LENGTH },
|
|
61
|
+
imageUrl: { bsonType: "string" },
|
|
62
|
+
createdAt: { bsonType: "date" },
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
updatedAt: { bsonType: "date" },
|
|
55
66
|
},
|
|
56
67
|
};
|
|
57
68
|
exports.muniIssuesIndexes = [
|
|
58
69
|
{ key: { callSid: 1 }, name: "callSid_1" },
|
|
59
70
|
{ key: { createdAt: -1 }, name: "createdAt_-1" },
|
|
71
|
+
// Complaint list: one municipality's issues, newest first.
|
|
72
|
+
{ key: { clientId: 1, createdAt: -1 }, name: "clientId_1_createdAt_-1" },
|
|
60
73
|
// Unique: one stored document per Jira ticket, guarding against duplicate writes.
|
|
61
74
|
{ key: { "jira.key": 1 }, name: "jira_key_unique", unique: true },
|
|
62
75
|
];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"muniIssues.schema.js","sourceRoot":"","sources":["../../../src/municipal/muniIssues/muniIssues.schema.ts"],"names":[],"mappings":";;;AACA,
|
|
1
|
+
{"version":3,"file":"muniIssues.schema.js","sourceRoot":"","sources":["../../../src/municipal/muniIssues/muniIssues.schema.ts"],"names":[],"mappings":";;;AACA,iEAKgC;AAEnB,QAAA,oBAAoB,GAAa;IAC5C,QAAQ,EAAE,QAAQ;IAClB,QAAQ,EAAE;QACR,SAAS;QACT,UAAU;QACV,UAAU;QACV,cAAc;QACd,MAAM;QACN,WAAW;KACZ;IACD,UAAU,EAAE;QACV,OAAO,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE;QAC/B,QAAQ,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE;QAChC,QAAQ,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE;QAChC,YAAY,EAAE;YACZ,QAAQ,EAAE,QAAQ;YAClB,QAAQ,EAAE;gBACR,YAAY;gBACZ,YAAY;gBACZ,kBAAkB;gBAClB,iBAAiB;aAClB;YACD,UAAU,EAAE;gBACV,UAAU,EAAE;oBACV,QAAQ,EAAE,QAAQ;oBAClB,SAAS,EAAE,iDAA0B;iBACtC;gBACD,UAAU,EAAE;oBACV,QAAQ,EAAE,QAAQ;oBAClB,SAAS,EAAE,iDAA0B;iBACtC;gBACD,gBAAgB,EAAE;oBAChB,QAAQ,EAAE,QAAQ;oBAClB,SAAS,EAAE,iDAA0B;iBACtC;gBACD,eAAe,EAAE;oBACf,QAAQ,EAAE,QAAQ;oBAClB,SAAS,EAAE,iDAA0B;iBACtC;aACF;SACF;QACD,IAAI,EAAE;YACJ,QAAQ,EAAE,QAAQ;YAClB,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC;YAC/B,UAAU,EAAE;gBACV,GAAG,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE;gBAC3B,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC,GAAG,+CAAwB,CAAC,EAAE;aACpD;SACF;QACD,SAAS,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE;QAC/B,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,GAAG,0CAAmB,CAAC,EAAE;QAC1C,QAAQ,EAAE;YACR,QAAQ,EAAE,QAAQ;YAClB,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,CAAC;YAC/B,UAAU,EAAE;gBACV,IAAI,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,iDAA0B,EAAE;gBACnE,QAAQ,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE;gBAChC,SAAS,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE;aAChC;SACF;QACD,SAAS,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE;KAChC;CACF,CAAC;AAEW,QAAA,iBAAiB,GAAuB;IACnD,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE;IAC1C,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE;IAChD,2DAA2D;IAC3D,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,yBAAyB,EAAE;IACxE,kFAAkF;IAClF,EAAE,GAAG,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,MAAM,EAAE,IAAI,EAAE;CAClE,CAAC"}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { ObjectId } from "mongodb";
|
|
2
|
-
import { CreateMuniIssueInput } from "./muniIssues.types";
|
|
2
|
+
import { CreateMuniIssueInput, MuniIssueStatus, SetMuniIssueResponseInput } from "./muniIssues.types";
|
|
3
3
|
export declare const createMuniIssue: (issueData: CreateMuniIssueInput, id: ObjectId) => Promise<ObjectId>;
|
|
4
|
+
/** Changes the status of an unanswered issue. Returns false if none matched. */
|
|
5
|
+
export declare const updateMuniIssueStatus: (id: string, clientId: string, status: MuniIssueStatus) => Promise<boolean>;
|
|
6
|
+
/** Saves the single TalkPilot response, which locks the issue. Returns false if none matched. */
|
|
7
|
+
export declare const setMuniIssueResponse: (id: string, clientId: string, { status, text, imageUrl }: SetMuniIssueResponseInput) => Promise<boolean>;
|
|
4
8
|
//# sourceMappingURL=muniIssues.setters.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"muniIssues.setters.d.ts","sourceRoot":"","sources":["../../../src/municipal/muniIssues/muniIssues.setters.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"muniIssues.setters.d.ts","sourceRoot":"","sources":["../../../src/municipal/muniIssues/muniIssues.setters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EACL,oBAAoB,EAEpB,eAAe,EACf,yBAAyB,EAC1B,MAAM,oBAAoB,CAAC;AAK5B,eAAO,MAAM,eAAe,GAC1B,WAAW,oBAAoB,EAC/B,IAAI,QAAQ,KACX,OAAO,CAAC,QAAQ,CAiBlB,CAAC;AAWF,gFAAgF;AAChF,eAAO,MAAM,qBAAqB,GAChC,IAAI,MAAM,EACV,UAAU,MAAM,EAChB,QAAQ,eAAe,KACtB,OAAO,CAAC,OAAO,CASjB,CAAC;AAEF,iGAAiG;AACjG,eAAO,MAAM,oBAAoB,GAC/B,IAAI,MAAM,EACV,UAAU,MAAM,EAChB,4BAA4B,yBAAyB,KACpD,OAAO,CAAC,OAAO,CAgBjB,CAAC"}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createMuniIssue = void 0;
|
|
3
|
+
exports.setMuniIssueResponse = exports.updateMuniIssueStatus = exports.createMuniIssue = void 0;
|
|
4
|
+
const mongodb_1 = require("mongodb");
|
|
4
5
|
const muniIssues_getters_1 = require("./muniIssues.getters");
|
|
5
6
|
const muniIssues_setup_1 = require("./muniIssues.setup");
|
|
6
7
|
// Required `id`: MIS pre-generates it (generateMuniIssueId) and embeds it in the Jira ticket — the two-way link.
|
|
7
8
|
const createMuniIssue = async (issueData, id) => {
|
|
8
9
|
await (0, muniIssues_setup_1.ensureMuniIssuesCollectionOnce)();
|
|
10
|
+
const now = new Date();
|
|
9
11
|
const issue = {
|
|
10
12
|
_id: id,
|
|
11
13
|
...issueData,
|
|
@@ -13,10 +15,45 @@ const createMuniIssue = async (issueData, id) => {
|
|
|
13
15
|
...issueData.jira,
|
|
14
16
|
syncStatus: issueData.jira.syncStatus ?? "created",
|
|
15
17
|
},
|
|
16
|
-
|
|
18
|
+
status: "in_review",
|
|
19
|
+
createdAt: now,
|
|
20
|
+
updatedAt: now,
|
|
17
21
|
};
|
|
18
22
|
const { insertedId } = await (0, muniIssues_getters_1.getMuniIssuesCollection)().insertOne(issue);
|
|
19
23
|
return insertedId;
|
|
20
24
|
};
|
|
21
25
|
exports.createMuniIssue = createMuniIssue;
|
|
26
|
+
// An issue of this client that is tracked (has a status) and not yet answered.
|
|
27
|
+
// The `response` condition is the lock: once answered, no further writes match.
|
|
28
|
+
const openIssueFilter = (id, clientId) => ({
|
|
29
|
+
_id: new mongodb_1.ObjectId(id),
|
|
30
|
+
clientId,
|
|
31
|
+
status: { $exists: true },
|
|
32
|
+
response: { $exists: false },
|
|
33
|
+
});
|
|
34
|
+
/** Changes the status of an unanswered issue. Returns false if none matched. */
|
|
35
|
+
const updateMuniIssueStatus = async (id, clientId, status) => {
|
|
36
|
+
if (!mongodb_1.ObjectId.isValid(id))
|
|
37
|
+
return false;
|
|
38
|
+
await (0, muniIssues_setup_1.ensureMuniIssuesCollectionOnce)();
|
|
39
|
+
const { matchedCount } = await (0, muniIssues_getters_1.getMuniIssuesCollection)().updateOne(openIssueFilter(id, clientId), { $set: { status, updatedAt: new Date() } });
|
|
40
|
+
return matchedCount > 0;
|
|
41
|
+
};
|
|
42
|
+
exports.updateMuniIssueStatus = updateMuniIssueStatus;
|
|
43
|
+
/** Saves the single TalkPilot response, which locks the issue. Returns false if none matched. */
|
|
44
|
+
const setMuniIssueResponse = async (id, clientId, { status, text, imageUrl }) => {
|
|
45
|
+
if (!mongodb_1.ObjectId.isValid(id))
|
|
46
|
+
return false;
|
|
47
|
+
await (0, muniIssues_setup_1.ensureMuniIssuesCollectionOnce)();
|
|
48
|
+
const now = new Date();
|
|
49
|
+
const { matchedCount } = await (0, muniIssues_getters_1.getMuniIssuesCollection)().updateOne(openIssueFilter(id, clientId), {
|
|
50
|
+
$set: {
|
|
51
|
+
status,
|
|
52
|
+
response: { text, ...(imageUrl && { imageUrl }), createdAt: now },
|
|
53
|
+
updatedAt: now,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
return matchedCount > 0;
|
|
57
|
+
};
|
|
58
|
+
exports.setMuniIssueResponse = setMuniIssueResponse;
|
|
22
59
|
//# sourceMappingURL=muniIssues.setters.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"muniIssues.setters.js","sourceRoot":"","sources":["../../../src/municipal/muniIssues/muniIssues.setters.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"muniIssues.setters.js","sourceRoot":"","sources":["../../../src/municipal/muniIssues/muniIssues.setters.ts"],"names":[],"mappings":";;;AAAA,qCAA2C;AAO3C,6DAA+D;AAC/D,yDAAoE;AAEpE,iHAAiH;AAC1G,MAAM,eAAe,GAAG,KAAK,EAClC,SAA+B,EAC/B,EAAY,EACO,EAAE;IACrB,MAAM,IAAA,iDAA8B,GAAE,CAAC;IAEvC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,MAAM,KAAK,GAAc;QACvB,GAAG,EAAE,EAAE;QACP,GAAG,SAAS;QACZ,IAAI,EAAE;YACJ,GAAG,SAAS,CAAC,IAAI;YACjB,UAAU,EAAE,SAAS,CAAC,IAAI,CAAC,UAAU,IAAI,SAAS;SACnD;QACD,MAAM,EAAE,WAAW;QACnB,SAAS,EAAE,GAAG;QACd,SAAS,EAAE,GAAG;KACf,CAAC;IACF,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,IAAA,4CAAuB,GAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACxE,OAAO,UAAU,CAAC;AACpB,CAAC,CAAC;AApBW,QAAA,eAAe,mBAoB1B;AAEF,+EAA+E;AAC/E,gFAAgF;AAChF,MAAM,eAAe,GAAG,CAAC,EAAU,EAAE,QAAgB,EAAqB,EAAE,CAAC,CAAC;IAC5E,GAAG,EAAE,IAAI,kBAAQ,CAAC,EAAE,CAAC;IACrB,QAAQ;IACR,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;IACzB,QAAQ,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE;CAC7B,CAAC,CAAC;AAEH,gFAAgF;AACzE,MAAM,qBAAqB,GAAG,KAAK,EACxC,EAAU,EACV,QAAgB,EAChB,MAAuB,EACL,EAAE;IACpB,IAAI,CAAC,kBAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAAE,OAAO,KAAK,CAAC;IACxC,MAAM,IAAA,iDAA8B,GAAE,CAAC;IAEvC,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,IAAA,4CAAuB,GAAE,CAAC,SAAS,CAChE,eAAe,CAAC,EAAE,EAAE,QAAQ,CAAC,EAC7B,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE,EAAE,CAC5C,CAAC;IACF,OAAO,YAAY,GAAG,CAAC,CAAC;AAC1B,CAAC,CAAC;AAbW,QAAA,qBAAqB,yBAahC;AAEF,iGAAiG;AAC1F,MAAM,oBAAoB,GAAG,KAAK,EACvC,EAAU,EACV,QAAgB,EAChB,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAA6B,EACnC,EAAE;IACpB,IAAI,CAAC,kBAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAAE,OAAO,KAAK,CAAC;IACxC,MAAM,IAAA,iDAA8B,GAAE,CAAC;IAEvC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,IAAA,4CAAuB,GAAE,CAAC,SAAS,CAChE,eAAe,CAAC,EAAE,EAAE,QAAQ,CAAC,EAC7B;QACE,IAAI,EAAE;YACJ,MAAM;YACN,QAAQ,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,QAAQ,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE;YACjE,SAAS,EAAE,GAAG;SACf;KACF,CACF,CAAC;IACF,OAAO,YAAY,GAAG,CAAC,CAAC;AAC1B,CAAC,CAAC;AApBW,QAAA,oBAAoB,wBAoB/B"}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { ObjectId } from "mongodb";
|
|
2
2
|
import { CityName } from "../utils/types";
|
|
3
|
-
import { MUNI_ISSUE_SYNC_STATUSES } from "./muniIssues.constants";
|
|
3
|
+
import { MUNI_ISSUE_STATUSES, MUNI_ISSUE_SYNC_STATUSES } from "./muniIssues.constants";
|
|
4
4
|
export type MuniIssueSyncStatus = (typeof MUNI_ISSUE_SYNC_STATUSES)[number];
|
|
5
|
+
export type MuniIssueStatus = (typeof MUNI_ISSUE_STATUSES)[number];
|
|
6
|
+
/** The single TalkPilot response; once set, the complaint is locked. */
|
|
7
|
+
export type MuniIssueResponse = {
|
|
8
|
+
text: string;
|
|
9
|
+
imageUrl?: string;
|
|
10
|
+
createdAt: Date;
|
|
11
|
+
};
|
|
5
12
|
export type MuniIssueContent = {
|
|
6
13
|
issuerName: string;
|
|
7
14
|
issuerRole: string;
|
|
@@ -20,11 +27,25 @@ export type MuniIssue = {
|
|
|
20
27
|
issueContent: MuniIssueContent;
|
|
21
28
|
jira: MuniIssueJira;
|
|
22
29
|
createdAt: Date;
|
|
30
|
+
status?: MuniIssueStatus;
|
|
31
|
+
response?: MuniIssueResponse;
|
|
32
|
+
updatedAt?: Date;
|
|
23
33
|
};
|
|
24
|
-
export type CreateMuniIssueInput = Omit<MuniIssue, "_id" | "createdAt" | "jira"> & {
|
|
34
|
+
export type CreateMuniIssueInput = Omit<MuniIssue, "_id" | "createdAt" | "jira" | "status" | "response" | "updatedAt"> & {
|
|
25
35
|
jira: {
|
|
26
36
|
key: string;
|
|
27
37
|
syncStatus?: MuniIssueSyncStatus;
|
|
28
38
|
};
|
|
29
39
|
};
|
|
40
|
+
export type SetMuniIssueResponseInput = {
|
|
41
|
+
status: MuniIssueStatus;
|
|
42
|
+
text: string;
|
|
43
|
+
imageUrl?: string;
|
|
44
|
+
};
|
|
45
|
+
/** `answered` filters by whether a TalkPilot response exists; omit it for all issues. */
|
|
46
|
+
export type FindMuniIssuesOptions = {
|
|
47
|
+
answered?: boolean;
|
|
48
|
+
skip?: number;
|
|
49
|
+
limit?: number;
|
|
50
|
+
};
|
|
30
51
|
//# sourceMappingURL=muniIssues.types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"muniIssues.types.d.ts","sourceRoot":"","sources":["../../../src/municipal/muniIssues/muniIssues.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,
|
|
1
|
+
{"version":3,"file":"muniIssues.types.d.ts","sourceRoot":"","sources":["../../../src/municipal/muniIssues/muniIssues.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACzB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5E,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnE,wEAAwE;AACxE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,IAAI,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,mBAAmB,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,GAAG,EAAE,QAAQ,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;IACnB,YAAY,EAAE,gBAAgB,CAAC;IAC/B,IAAI,EAAE,aAAa,CAAC;IACpB,SAAS,EAAE,IAAI,CAAC;IAGhB,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,SAAS,CAAC,EAAE,IAAI,CAAC;CAClB,CAAC;AAIF,MAAM,MAAM,oBAAoB,GAAG,IAAI,CACrC,SAAS,EACT,KAAK,GAAG,WAAW,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,WAAW,CACnE,GAAG;IACF,IAAI,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,mBAAmB,CAAA;KAAE,CAAC;CACzD,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,MAAM,EAAE,eAAe,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,yFAAyF;AACzF,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC"}
|
|
@@ -32,4 +32,10 @@ export declare const CallStatusValues: {
|
|
|
32
32
|
readonly Canceled: "canceled";
|
|
33
33
|
};
|
|
34
34
|
export declare const ALL_CALL_STATUSES: ("in-progress" | "answered" | "no-answer" | "completed" | "busy" | "failed" | "canceled" | "initiated" | "ringing")[];
|
|
35
|
+
/** Who ended a connected call: our platform, the bot (end flow), or the caller. */
|
|
36
|
+
export declare const CallEndedByValues: {
|
|
37
|
+
readonly System: "system";
|
|
38
|
+
readonly Agent: "agent";
|
|
39
|
+
readonly User: "user";
|
|
40
|
+
};
|
|
35
41
|
//# sourceMappingURL=calls.constants.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"calls.constants.d.ts","sourceRoot":"","sources":["../../../src/talkpilot/calls/calls.constants.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,eAAO,MAAM,wBAAwB,EAAG,UAAmB,CAAC;AAE5D,gDAAgD;AAChD,eAAO,MAAM,0BAA0B,EAAG,YAAqB,CAAC;AAEhE,wEAAwE;AACxE,eAAO,MAAM,gBAAgB,sCAAuC,CAAC;AAErE,4EAA4E;AAC5E,eAAO,MAAM,cAAc,mEAMjB,CAAC;AAEX,6BAA6B;AAC7B,eAAO,MAAM,aAAa,KAAK,CAAC;AAEhC,iEAAiE;AACjE,eAAO,MAAM,iBAAiB,QAAS,CAAC;AAExC;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAC/C,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAE9C,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAC9C,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAEhD,eAAO,MAAM,gBAAgB;;;;;;;;;;CAUnB,CAAC;AAEX,eAAO,MAAM,iBAAiB,uHAAkC,CAAC"}
|
|
1
|
+
{"version":3,"file":"calls.constants.d.ts","sourceRoot":"","sources":["../../../src/talkpilot/calls/calls.constants.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,eAAO,MAAM,wBAAwB,EAAG,UAAmB,CAAC;AAE5D,gDAAgD;AAChD,eAAO,MAAM,0BAA0B,EAAG,YAAqB,CAAC;AAEhE,wEAAwE;AACxE,eAAO,MAAM,gBAAgB,sCAAuC,CAAC;AAErE,4EAA4E;AAC5E,eAAO,MAAM,cAAc,mEAMjB,CAAC;AAEX,6BAA6B;AAC7B,eAAO,MAAM,aAAa,KAAK,CAAC;AAEhC,iEAAiE;AACjE,eAAO,MAAM,iBAAiB,QAAS,CAAC;AAExC;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAC/C,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAE9C,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAC9C,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAEhD,eAAO,MAAM,gBAAgB;;;;;;;;;;CAUnB,CAAC;AAEX,eAAO,MAAM,iBAAiB,uHAAkC,CAAC;AAEjE,mFAAmF;AACnF,eAAO,MAAM,iBAAiB;;;;CAIpB,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ALL_CALL_STATUSES = exports.CallStatusValues = exports.TOOL_EXECUTIONS_DEFAULT_LIMIT = exports.TOOL_EXECUTIONS_DEFAULT_SKIP = exports.TREND_WEEK_BUCKET_MIN_MONTHS = exports.TREND_MONTH_BUCKET_MIN_MONTHS = exports.STATS_MAX_TIME_MS = exports.HOURS_PER_DAY = exports.FINAL_STATUSES = exports.IN_CALL_STATUSES = exports.CONFERENCE_ROLE_SUPERVISOR = exports.CONFERENCE_ROLE_CUSTOMER = void 0;
|
|
3
|
+
exports.CallEndedByValues = exports.ALL_CALL_STATUSES = exports.CallStatusValues = exports.TOOL_EXECUTIONS_DEFAULT_LIMIT = exports.TOOL_EXECUTIONS_DEFAULT_SKIP = exports.TREND_WEEK_BUCKET_MIN_MONTHS = exports.TREND_MONTH_BUCKET_MIN_MONTHS = exports.STATS_MAX_TIME_MS = exports.HOURS_PER_DAY = exports.FINAL_STATUSES = exports.IN_CALL_STATUSES = exports.CONFERENCE_ROLE_SUPERVISOR = exports.CONFERENCE_ROLE_CUSTOMER = void 0;
|
|
4
4
|
/** Customer role within a conference call. */
|
|
5
5
|
exports.CONFERENCE_ROLE_CUSTOMER = "customer";
|
|
6
6
|
/** Supervisor role within a conference call. */
|
|
@@ -41,4 +41,10 @@ exports.CallStatusValues = {
|
|
|
41
41
|
Canceled: "canceled",
|
|
42
42
|
};
|
|
43
43
|
exports.ALL_CALL_STATUSES = Object.values(exports.CallStatusValues);
|
|
44
|
+
/** Who ended a connected call: our platform, the bot (end flow), or the caller. */
|
|
45
|
+
exports.CallEndedByValues = {
|
|
46
|
+
System: "system",
|
|
47
|
+
Agent: "agent",
|
|
48
|
+
User: "user",
|
|
49
|
+
};
|
|
44
50
|
//# sourceMappingURL=calls.constants.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"calls.constants.js","sourceRoot":"","sources":["../../../src/talkpilot/calls/calls.constants.ts"],"names":[],"mappings":";;;AAAA,8CAA8C;AACjC,QAAA,wBAAwB,GAAG,UAAmB,CAAC;AAE5D,gDAAgD;AACnC,QAAA,0BAA0B,GAAG,YAAqB,CAAC;AAEhE,wEAAwE;AAC3D,QAAA,gBAAgB,GAAG,CAAC,aAAa,EAAE,UAAU,CAAU,CAAC;AAErE,4EAA4E;AAC/D,QAAA,cAAc,GAAG;IAC5B,WAAW;IACX,WAAW;IACX,MAAM;IACN,QAAQ;IACR,UAAU;CACF,CAAC;AAEX,6BAA6B;AAChB,QAAA,aAAa,GAAG,EAAE,CAAC;AAEhC,iEAAiE;AACpD,QAAA,iBAAiB,GAAG,MAAM,CAAC;AAExC;;;;;GAKG;AACU,QAAA,6BAA6B,GAAG,CAAC,CAAC;AAClC,QAAA,4BAA4B,GAAG,CAAC,CAAC;AAEjC,QAAA,4BAA4B,GAAG,CAAC,CAAC;AACjC,QAAA,6BAA6B,GAAG,EAAE,CAAC;AAEnC,QAAA,gBAAgB,GAAG;IAC9B,SAAS,EAAE,WAAW;IACtB,OAAO,EAAE,SAAS;IAClB,UAAU,EAAE,aAAa;IACzB,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,WAAW;IACtB,IAAI,EAAE,MAAM;IACZ,QAAQ,EAAE,WAAW;IACrB,MAAM,EAAE,QAAQ;IAChB,QAAQ,EAAE,UAAU;CACZ,CAAC;AAEE,QAAA,iBAAiB,GAAG,MAAM,CAAC,MAAM,CAAC,wBAAgB,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"calls.constants.js","sourceRoot":"","sources":["../../../src/talkpilot/calls/calls.constants.ts"],"names":[],"mappings":";;;AAAA,8CAA8C;AACjC,QAAA,wBAAwB,GAAG,UAAmB,CAAC;AAE5D,gDAAgD;AACnC,QAAA,0BAA0B,GAAG,YAAqB,CAAC;AAEhE,wEAAwE;AAC3D,QAAA,gBAAgB,GAAG,CAAC,aAAa,EAAE,UAAU,CAAU,CAAC;AAErE,4EAA4E;AAC/D,QAAA,cAAc,GAAG;IAC5B,WAAW;IACX,WAAW;IACX,MAAM;IACN,QAAQ;IACR,UAAU;CACF,CAAC;AAEX,6BAA6B;AAChB,QAAA,aAAa,GAAG,EAAE,CAAC;AAEhC,iEAAiE;AACpD,QAAA,iBAAiB,GAAG,MAAM,CAAC;AAExC;;;;;GAKG;AACU,QAAA,6BAA6B,GAAG,CAAC,CAAC;AAClC,QAAA,4BAA4B,GAAG,CAAC,CAAC;AAEjC,QAAA,4BAA4B,GAAG,CAAC,CAAC;AACjC,QAAA,6BAA6B,GAAG,EAAE,CAAC;AAEnC,QAAA,gBAAgB,GAAG;IAC9B,SAAS,EAAE,WAAW;IACtB,OAAO,EAAE,SAAS;IAClB,UAAU,EAAE,aAAa;IACzB,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,WAAW;IACtB,IAAI,EAAE,MAAM;IACZ,QAAQ,EAAE,WAAW;IACrB,MAAM,EAAE,QAAQ;IAChB,QAAQ,EAAE,UAAU;CACZ,CAAC;AAEE,QAAA,iBAAiB,GAAG,MAAM,CAAC,MAAM,CAAC,wBAAgB,CAAC,CAAC;AAEjE,mFAAmF;AACtE,QAAA,iBAAiB,GAAG;IAC/B,MAAM,EAAE,QAAQ;IAChB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,MAAM;CACJ,CAAC"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ObjectId, Sort, WithId } from "mongodb";
|
|
2
2
|
import { TranscriptionSegment } from "../results";
|
|
3
3
|
import { LeadProperty } from "../leads";
|
|
4
|
-
import { CallStatusValues, CONFERENCE_ROLE_CUSTOMER, CONFERENCE_ROLE_SUPERVISOR } from "./calls.constants";
|
|
4
|
+
import { CallEndedByValues, CallStatusValues, CONFERENCE_ROLE_CUSTOMER, CONFERENCE_ROLE_SUPERVISOR } from "./calls.constants";
|
|
5
5
|
export type ConferenceRole = typeof CONFERENCE_ROLE_CUSTOMER | typeof CONFERENCE_ROLE_SUPERVISOR | null;
|
|
6
6
|
export type CallStatus = (typeof CallStatusValues)[keyof typeof CallStatusValues];
|
|
7
|
-
export type CallEndedBy =
|
|
7
|
+
export type CallEndedBy = (typeof CallEndedByValues)[keyof typeof CallEndedByValues];
|
|
8
8
|
export type Call = {
|
|
9
9
|
callSid: string;
|
|
10
10
|
flowId: ObjectId;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"calls.types.d.ts","sourceRoot":"","sources":["../../../src/talkpilot/calls/calls.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,OAAO,EACL,gBAAgB,EAChB,wBAAwB,EACxB,0BAA0B,EAC3B,MAAM,mBAAmB,CAAC;AAE3B,MAAM,MAAM,cAAc,GACtB,OAAO,wBAAwB,GAC/B,OAAO,0BAA0B,GACjC,IAAI,CAAC;AAET,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,OAAO,gBAAgB,CAAC,CAAC;AAElF,MAAM,MAAM,WAAW,GAAG,
|
|
1
|
+
{"version":3,"file":"calls.types.d.ts","sourceRoot":"","sources":["../../../src/talkpilot/calls/calls.types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { createMuniIssue, setMuniIssueResponse } from "../muniIssues.setters";
|
|
2
|
+
import {
|
|
3
|
+
countMuniIssuesByClient,
|
|
4
|
+
findMuniIssuesByClient,
|
|
5
|
+
generateMuniIssueId,
|
|
6
|
+
getMuniIssueByIdForClient,
|
|
7
|
+
getMuniIssuesCollection,
|
|
8
|
+
} from "../muniIssues.getters";
|
|
9
|
+
import { createMuniIssueInput } from "../../../test-utils/factories";
|
|
10
|
+
|
|
11
|
+
const CLIENT = "client-a";
|
|
12
|
+
|
|
13
|
+
const createIssue = async (clientId = CLIENT) =>
|
|
14
|
+
(await createMuniIssue(createMuniIssueInput({ clientId }), generateMuniIssueId())).toString();
|
|
15
|
+
|
|
16
|
+
describe("muniIssues getters (complaint tracking)", () => {
|
|
17
|
+
describe("findMuniIssuesByClient / countMuniIssuesByClient", () => {
|
|
18
|
+
it("should return only the client's tracked issues, newest first", async () => {
|
|
19
|
+
const older = await createIssue();
|
|
20
|
+
const newer = await createIssue();
|
|
21
|
+
await createIssue("client-b");
|
|
22
|
+
// An issue created before complaint tracking has no status.
|
|
23
|
+
await getMuniIssuesCollection().insertOne({
|
|
24
|
+
...createMuniIssueInput({ clientId: CLIENT }),
|
|
25
|
+
_id: generateMuniIssueId(),
|
|
26
|
+
jira: { key: "CP-legacy", syncStatus: "created" },
|
|
27
|
+
createdAt: new Date(),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const issues = await findMuniIssuesByClient(CLIENT);
|
|
31
|
+
|
|
32
|
+
expect(issues.map((i) => i._id.toString())).toEqual([newer, older]);
|
|
33
|
+
expect(await countMuniIssuesByClient(CLIENT)).toBe(2);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should filter by answered and apply skip/limit", async () => {
|
|
37
|
+
const answered = await createIssue();
|
|
38
|
+
await createIssue();
|
|
39
|
+
await createIssue();
|
|
40
|
+
await setMuniIssueResponse(answered, CLIENT, { status: "resolved", text: "Done" });
|
|
41
|
+
|
|
42
|
+
expect(await countMuniIssuesByClient(CLIENT, { answered: true })).toBe(1);
|
|
43
|
+
expect(await countMuniIssuesByClient(CLIENT, { answered: false })).toBe(2);
|
|
44
|
+
expect(
|
|
45
|
+
(await findMuniIssuesByClient(CLIENT, { answered: true }))[0]._id.toString(),
|
|
46
|
+
).toBe(answered);
|
|
47
|
+
expect(await findMuniIssuesByClient(CLIENT, { skip: 1, limit: 1 })).toHaveLength(1);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("getMuniIssueByIdForClient", () => {
|
|
52
|
+
it("should return the issue only for its own client", async () => {
|
|
53
|
+
const id = await createIssue();
|
|
54
|
+
|
|
55
|
+
expect((await getMuniIssueByIdForClient(id, CLIENT))?._id.toString()).toBe(id);
|
|
56
|
+
expect(await getMuniIssueByIdForClient(id, "client-b")).toBeNull();
|
|
57
|
+
expect(await getMuniIssueByIdForClient("not-a-valid-id", CLIENT)).toBeNull();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createMuniIssue,
|
|
3
|
+
setMuniIssueResponse,
|
|
4
|
+
updateMuniIssueStatus,
|
|
5
|
+
} from "../muniIssues.setters";
|
|
2
6
|
import {
|
|
3
7
|
generateMuniIssueId,
|
|
4
8
|
getMuniIssueById,
|
|
@@ -25,6 +29,18 @@ describe("muniIssues setters", () => {
|
|
|
25
29
|
expect(saved?.createdAt).toBeInstanceOf(Date);
|
|
26
30
|
});
|
|
27
31
|
|
|
32
|
+
it("should start the issue as in_review with updatedAt and no response", async () => {
|
|
33
|
+
const insertedId = await createMuniIssue(
|
|
34
|
+
createMuniIssueInput(),
|
|
35
|
+
generateMuniIssueId(),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const saved = await getMuniIssueById(insertedId.toString());
|
|
39
|
+
expect(saved?.status).toBe("in_review");
|
|
40
|
+
expect(saved?.updatedAt).toBeInstanceOf(Date);
|
|
41
|
+
expect(saved?.response).toBeUndefined();
|
|
42
|
+
});
|
|
43
|
+
|
|
28
44
|
it("should default jira.syncStatus to created when not provided", async () => {
|
|
29
45
|
const id = generateMuniIssueId();
|
|
30
46
|
|
|
@@ -91,4 +107,61 @@ describe("muniIssues setters", () => {
|
|
|
91
107
|
expect(await getMuniIssueByJiraKey("CP-nope")).toBeNull();
|
|
92
108
|
});
|
|
93
109
|
});
|
|
110
|
+
|
|
111
|
+
describe("updateMuniIssueStatus", () => {
|
|
112
|
+
it("should change the status of an unanswered issue", async () => {
|
|
113
|
+
const input = createMuniIssueInput();
|
|
114
|
+
const id = (await createMuniIssue(input, generateMuniIssueId())).toString();
|
|
115
|
+
|
|
116
|
+
expect(await updateMuniIssueStatus(id, input.clientId, "in_progress")).toBe(true);
|
|
117
|
+
expect((await getMuniIssueById(id))?.status).toBe("in_progress");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should not touch another client's issue or a malformed id", async () => {
|
|
121
|
+
const id = (
|
|
122
|
+
await createMuniIssue(createMuniIssueInput(), generateMuniIssueId())
|
|
123
|
+
).toString();
|
|
124
|
+
|
|
125
|
+
expect(await updateMuniIssueStatus(id, "other-client", "resolved")).toBe(false);
|
|
126
|
+
expect(await updateMuniIssueStatus("not-a-valid-id", "any", "resolved")).toBe(false);
|
|
127
|
+
expect((await getMuniIssueById(id))?.status).toBe("in_review");
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe("setMuniIssueResponse", () => {
|
|
132
|
+
it("should save the response and status", async () => {
|
|
133
|
+
const input = createMuniIssueInput();
|
|
134
|
+
const id = (await createMuniIssue(input, generateMuniIssueId())).toString();
|
|
135
|
+
|
|
136
|
+
const saved = await setMuniIssueResponse(id, input.clientId, {
|
|
137
|
+
status: "resolved",
|
|
138
|
+
text: "Fixed",
|
|
139
|
+
imageUrl: "https://example.com/a.png",
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(saved).toBe(true);
|
|
143
|
+
const issue = await getMuniIssueById(id);
|
|
144
|
+
expect(issue?.status).toBe("resolved");
|
|
145
|
+
expect(issue?.response).toMatchObject({
|
|
146
|
+
text: "Fixed",
|
|
147
|
+
imageUrl: "https://example.com/a.png",
|
|
148
|
+
});
|
|
149
|
+
expect(issue?.response?.createdAt).toBeInstanceOf(Date);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("should lock the issue: no second response and no status change", async () => {
|
|
153
|
+
const input = createMuniIssueInput();
|
|
154
|
+
const id = (await createMuniIssue(input, generateMuniIssueId())).toString();
|
|
155
|
+
await setMuniIssueResponse(id, input.clientId, { status: "resolved", text: "First" });
|
|
156
|
+
|
|
157
|
+
expect(
|
|
158
|
+
await setMuniIssueResponse(id, input.clientId, { status: "in_progress", text: "Second" }),
|
|
159
|
+
).toBe(false);
|
|
160
|
+
expect(await updateMuniIssueStatus(id, input.clientId, "in_progress")).toBe(false);
|
|
161
|
+
|
|
162
|
+
const issue = await getMuniIssueById(id);
|
|
163
|
+
expect(issue?.status).toBe("resolved");
|
|
164
|
+
expect(issue?.response?.text).toBe("First");
|
|
165
|
+
});
|
|
166
|
+
});
|
|
94
167
|
});
|
|
@@ -3,6 +3,7 @@ export * from "./muniIssues.setters";
|
|
|
3
3
|
export * from "./muniIssues.setup";
|
|
4
4
|
export {
|
|
5
5
|
MUNI_ISSUES_COLLECTION,
|
|
6
|
+
MUNI_ISSUE_STATUSES,
|
|
6
7
|
MUNI_ISSUE_SYNC_STATUSES,
|
|
7
8
|
MUNI_ISSUE_TEXT_MAX_LENGTH,
|
|
8
9
|
MUNI_ISSUE_NAME_MAX_LENGTH,
|
|
@@ -13,5 +14,9 @@ export type {
|
|
|
13
14
|
MuniIssueContent,
|
|
14
15
|
MuniIssueJira,
|
|
15
16
|
MuniIssueSyncStatus,
|
|
17
|
+
MuniIssueStatus,
|
|
18
|
+
MuniIssueResponse,
|
|
16
19
|
CreateMuniIssueInput,
|
|
20
|
+
SetMuniIssueResponseInput,
|
|
21
|
+
FindMuniIssuesOptions,
|
|
17
22
|
} from "./muniIssues.types";
|
|
@@ -4,6 +4,14 @@ export const MUNI_ISSUES_COLLECTION = "muniIssues";
|
|
|
4
4
|
/** Sync state of the issue's Jira ticket at write time. */
|
|
5
5
|
export const MUNI_ISSUE_SYNC_STATUSES = ["created", "failed"] as const;
|
|
6
6
|
|
|
7
|
+
/** Customer-facing complaint status, set manually by the TalkPilot team. */
|
|
8
|
+
export const MUNI_ISSUE_STATUSES = [
|
|
9
|
+
"in_review",
|
|
10
|
+
"in_progress",
|
|
11
|
+
"resolved",
|
|
12
|
+
"no_issue_found",
|
|
13
|
+
] as const;
|
|
14
|
+
|
|
7
15
|
/** Max length for the long free-text fields — well above the ~2000-char target. */
|
|
8
16
|
export const MUNI_ISSUE_TEXT_MAX_LENGTH = 5000;
|
|
9
17
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Collection, ObjectId } from "mongodb";
|
|
1
|
+
import { Collection, Filter, ObjectId } from "mongodb";
|
|
2
2
|
import { getDb } from "../index";
|
|
3
|
-
import { MuniIssue } from "./muniIssues.types";
|
|
3
|
+
import { FindMuniIssuesOptions, MuniIssue } from "./muniIssues.types";
|
|
4
4
|
import { MUNI_ISSUES_COLLECTION } from "./muniIssues.constants";
|
|
5
5
|
|
|
6
6
|
export const getMuniIssuesCollection = (): Collection<MuniIssue> => {
|
|
@@ -22,3 +22,47 @@ export const getMuniIssueByJiraKey = async (
|
|
|
22
22
|
): Promise<MuniIssue | null> => {
|
|
23
23
|
return getMuniIssuesCollection().findOne({ "jira.key": jiraKey });
|
|
24
24
|
};
|
|
25
|
+
|
|
26
|
+
// Tracked issues of one client. Issues without a status predate complaint tracking and are excluded.
|
|
27
|
+
const clientIssuesFilter = (
|
|
28
|
+
clientId: string,
|
|
29
|
+
answered?: boolean,
|
|
30
|
+
): Filter<MuniIssue> => ({
|
|
31
|
+
clientId,
|
|
32
|
+
status: { $exists: true },
|
|
33
|
+
...(answered !== undefined && { response: { $exists: answered } }),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/** A client's tracked issues, newest first. */
|
|
37
|
+
export const findMuniIssuesByClient = async (
|
|
38
|
+
clientId: string,
|
|
39
|
+
{ answered, skip = 0, limit = 0 }: FindMuniIssuesOptions = {},
|
|
40
|
+
): Promise<MuniIssue[]> => {
|
|
41
|
+
return getMuniIssuesCollection()
|
|
42
|
+
.find(clientIssuesFilter(clientId, answered))
|
|
43
|
+
.sort({ createdAt: -1 })
|
|
44
|
+
.skip(skip)
|
|
45
|
+
.limit(limit)
|
|
46
|
+
.toArray();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const countMuniIssuesByClient = async (
|
|
50
|
+
clientId: string,
|
|
51
|
+
{ answered }: Pick<FindMuniIssuesOptions, "answered"> = {},
|
|
52
|
+
): Promise<number> => {
|
|
53
|
+
return getMuniIssuesCollection().countDocuments(
|
|
54
|
+
clientIssuesFilter(clientId, answered),
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** One tracked issue, only if it belongs to the client. */
|
|
59
|
+
export const getMuniIssueByIdForClient = async (
|
|
60
|
+
id: string,
|
|
61
|
+
clientId: string,
|
|
62
|
+
): Promise<MuniIssue | null> => {
|
|
63
|
+
if (!ObjectId.isValid(id)) return null;
|
|
64
|
+
return getMuniIssuesCollection().findOne({
|
|
65
|
+
...clientIssuesFilter(clientId),
|
|
66
|
+
_id: new ObjectId(id),
|
|
67
|
+
});
|
|
68
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Document, IndexDescription } from "mongodb";
|
|
2
2
|
import {
|
|
3
|
+
MUNI_ISSUE_STATUSES,
|
|
3
4
|
MUNI_ISSUE_SYNC_STATUSES,
|
|
4
5
|
MUNI_ISSUE_TEXT_MAX_LENGTH,
|
|
5
6
|
MUNI_ISSUE_NAME_MAX_LENGTH,
|
|
@@ -55,12 +56,25 @@ export const muniIssuesJsonSchema: Document = {
|
|
|
55
56
|
},
|
|
56
57
|
},
|
|
57
58
|
createdAt: { bsonType: "date" },
|
|
59
|
+
status: { enum: [...MUNI_ISSUE_STATUSES] },
|
|
60
|
+
response: {
|
|
61
|
+
bsonType: "object",
|
|
62
|
+
required: ["text", "createdAt"],
|
|
63
|
+
properties: {
|
|
64
|
+
text: { bsonType: "string", maxLength: MUNI_ISSUE_TEXT_MAX_LENGTH },
|
|
65
|
+
imageUrl: { bsonType: "string" },
|
|
66
|
+
createdAt: { bsonType: "date" },
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
updatedAt: { bsonType: "date" },
|
|
58
70
|
},
|
|
59
71
|
};
|
|
60
72
|
|
|
61
73
|
export const muniIssuesIndexes: IndexDescription[] = [
|
|
62
74
|
{ key: { callSid: 1 }, name: "callSid_1" },
|
|
63
75
|
{ key: { createdAt: -1 }, name: "createdAt_-1" },
|
|
76
|
+
// Complaint list: one municipality's issues, newest first.
|
|
77
|
+
{ key: { clientId: 1, createdAt: -1 }, name: "clientId_1_createdAt_-1" },
|
|
64
78
|
// Unique: one stored document per Jira ticket, guarding against duplicate writes.
|
|
65
79
|
{ key: { "jira.key": 1 }, name: "jira_key_unique", unique: true },
|
|
66
80
|
];
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import { ObjectId } from "mongodb";
|
|
2
|
-
import {
|
|
1
|
+
import { Filter, ObjectId } from "mongodb";
|
|
2
|
+
import {
|
|
3
|
+
CreateMuniIssueInput,
|
|
4
|
+
MuniIssue,
|
|
5
|
+
MuniIssueStatus,
|
|
6
|
+
SetMuniIssueResponseInput,
|
|
7
|
+
} from "./muniIssues.types";
|
|
3
8
|
import { getMuniIssuesCollection } from "./muniIssues.getters";
|
|
4
9
|
import { ensureMuniIssuesCollectionOnce } from "./muniIssues.setup";
|
|
5
10
|
|
|
@@ -10,6 +15,7 @@ export const createMuniIssue = async (
|
|
|
10
15
|
): Promise<ObjectId> => {
|
|
11
16
|
await ensureMuniIssuesCollectionOnce();
|
|
12
17
|
|
|
18
|
+
const now = new Date();
|
|
13
19
|
const issue: MuniIssue = {
|
|
14
20
|
_id: id,
|
|
15
21
|
...issueData,
|
|
@@ -17,8 +23,58 @@ export const createMuniIssue = async (
|
|
|
17
23
|
...issueData.jira,
|
|
18
24
|
syncStatus: issueData.jira.syncStatus ?? "created",
|
|
19
25
|
},
|
|
20
|
-
|
|
26
|
+
status: "in_review",
|
|
27
|
+
createdAt: now,
|
|
28
|
+
updatedAt: now,
|
|
21
29
|
};
|
|
22
30
|
const { insertedId } = await getMuniIssuesCollection().insertOne(issue);
|
|
23
31
|
return insertedId;
|
|
24
32
|
};
|
|
33
|
+
|
|
34
|
+
// An issue of this client that is tracked (has a status) and not yet answered.
|
|
35
|
+
// The `response` condition is the lock: once answered, no further writes match.
|
|
36
|
+
const openIssueFilter = (id: string, clientId: string): Filter<MuniIssue> => ({
|
|
37
|
+
_id: new ObjectId(id),
|
|
38
|
+
clientId,
|
|
39
|
+
status: { $exists: true },
|
|
40
|
+
response: { $exists: false },
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/** Changes the status of an unanswered issue. Returns false if none matched. */
|
|
44
|
+
export const updateMuniIssueStatus = async (
|
|
45
|
+
id: string,
|
|
46
|
+
clientId: string,
|
|
47
|
+
status: MuniIssueStatus,
|
|
48
|
+
): Promise<boolean> => {
|
|
49
|
+
if (!ObjectId.isValid(id)) return false;
|
|
50
|
+
await ensureMuniIssuesCollectionOnce();
|
|
51
|
+
|
|
52
|
+
const { matchedCount } = await getMuniIssuesCollection().updateOne(
|
|
53
|
+
openIssueFilter(id, clientId),
|
|
54
|
+
{ $set: { status, updatedAt: new Date() } },
|
|
55
|
+
);
|
|
56
|
+
return matchedCount > 0;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** Saves the single TalkPilot response, which locks the issue. Returns false if none matched. */
|
|
60
|
+
export const setMuniIssueResponse = async (
|
|
61
|
+
id: string,
|
|
62
|
+
clientId: string,
|
|
63
|
+
{ status, text, imageUrl }: SetMuniIssueResponseInput,
|
|
64
|
+
): Promise<boolean> => {
|
|
65
|
+
if (!ObjectId.isValid(id)) return false;
|
|
66
|
+
await ensureMuniIssuesCollectionOnce();
|
|
67
|
+
|
|
68
|
+
const now = new Date();
|
|
69
|
+
const { matchedCount } = await getMuniIssuesCollection().updateOne(
|
|
70
|
+
openIssueFilter(id, clientId),
|
|
71
|
+
{
|
|
72
|
+
$set: {
|
|
73
|
+
status,
|
|
74
|
+
response: { text, ...(imageUrl && { imageUrl }), createdAt: now },
|
|
75
|
+
updatedAt: now,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
return matchedCount > 0;
|
|
80
|
+
};
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import { ObjectId } from "mongodb";
|
|
2
2
|
|
|
3
3
|
import { CityName } from "../utils/types";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
MUNI_ISSUE_STATUSES,
|
|
6
|
+
MUNI_ISSUE_SYNC_STATUSES,
|
|
7
|
+
} from "./muniIssues.constants";
|
|
5
8
|
|
|
6
9
|
export type MuniIssueSyncStatus = (typeof MUNI_ISSUE_SYNC_STATUSES)[number];
|
|
7
10
|
|
|
11
|
+
export type MuniIssueStatus = (typeof MUNI_ISSUE_STATUSES)[number];
|
|
12
|
+
|
|
13
|
+
/** The single TalkPilot response; once set, the complaint is locked. */
|
|
14
|
+
export type MuniIssueResponse = {
|
|
15
|
+
text: string;
|
|
16
|
+
imageUrl?: string;
|
|
17
|
+
createdAt: Date;
|
|
18
|
+
};
|
|
19
|
+
|
|
8
20
|
export type MuniIssueContent = {
|
|
9
21
|
issuerName: string;
|
|
10
22
|
issuerRole: string;
|
|
@@ -25,13 +37,31 @@ export type MuniIssue = {
|
|
|
25
37
|
issueContent: MuniIssueContent;
|
|
26
38
|
jira: MuniIssueJira;
|
|
27
39
|
createdAt: Date;
|
|
40
|
+
// Complaint tracking (CP-1888). Optional: issues created before it have none
|
|
41
|
+
// of these, which is how they are kept out of tracking.
|
|
42
|
+
status?: MuniIssueStatus;
|
|
43
|
+
response?: MuniIssueResponse;
|
|
44
|
+
updatedAt?: Date;
|
|
28
45
|
};
|
|
29
46
|
|
|
30
|
-
// _id/createdAt are set on insert; jira.syncStatus defaults to
|
|
31
|
-
// because the issue is only stored after its Jira ticket exists (CP-1115).
|
|
47
|
+
// _id/createdAt/status/updatedAt are set on insert; jira.syncStatus defaults to
|
|
48
|
+
// "created" because the issue is only stored after its Jira ticket exists (CP-1115).
|
|
32
49
|
export type CreateMuniIssueInput = Omit<
|
|
33
50
|
MuniIssue,
|
|
34
|
-
"_id" | "createdAt" | "jira"
|
|
51
|
+
"_id" | "createdAt" | "jira" | "status" | "response" | "updatedAt"
|
|
35
52
|
> & {
|
|
36
53
|
jira: { key: string; syncStatus?: MuniIssueSyncStatus };
|
|
37
54
|
};
|
|
55
|
+
|
|
56
|
+
export type SetMuniIssueResponseInput = {
|
|
57
|
+
status: MuniIssueStatus;
|
|
58
|
+
text: string;
|
|
59
|
+
imageUrl?: string;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** `answered` filters by whether a TalkPilot response exists; omit it for all issues. */
|
|
63
|
+
export type FindMuniIssuesOptions = {
|
|
64
|
+
answered?: boolean;
|
|
65
|
+
skip?: number;
|
|
66
|
+
limit?: number;
|
|
67
|
+
};
|
|
@@ -80,11 +80,11 @@ describe("db.calls", () => {
|
|
|
80
80
|
const call = createOutGoingCallDoc();
|
|
81
81
|
await createCallDoc(call);
|
|
82
82
|
|
|
83
|
-
const written = await setCallEndedBy(call.callSid, "
|
|
83
|
+
const written = await setCallEndedBy(call.callSid, "user");
|
|
84
84
|
|
|
85
85
|
expect(written).toBe(true);
|
|
86
86
|
expect(await getCallByCallSid(call.callSid)).toMatchObject({
|
|
87
|
-
endedBy: "
|
|
87
|
+
endedBy: "user",
|
|
88
88
|
});
|
|
89
89
|
});
|
|
90
90
|
|
|
@@ -92,12 +92,12 @@ describe("db.calls", () => {
|
|
|
92
92
|
const call = createOutGoingCallDoc();
|
|
93
93
|
await createCallDoc(call);
|
|
94
94
|
|
|
95
|
-
await setCallEndedBy(call.callSid, "
|
|
96
|
-
const written = await setCallEndedBy(call.callSid, "
|
|
95
|
+
await setCallEndedBy(call.callSid, "user");
|
|
96
|
+
const written = await setCallEndedBy(call.callSid, "agent");
|
|
97
97
|
|
|
98
98
|
expect(written).toBe(false);
|
|
99
99
|
expect(await getCallByCallSid(call.callSid)).toMatchObject({
|
|
100
|
-
endedBy: "
|
|
100
|
+
endedBy: "user",
|
|
101
101
|
});
|
|
102
102
|
});
|
|
103
103
|
|
|
@@ -106,13 +106,13 @@ describe("db.calls", () => {
|
|
|
106
106
|
await createCallDoc(call);
|
|
107
107
|
|
|
108
108
|
const results = await Promise.all([
|
|
109
|
-
setCallEndedBy(call.callSid, "
|
|
109
|
+
setCallEndedBy(call.callSid, "user"),
|
|
110
110
|
setCallEndedBy(call.callSid, "system"),
|
|
111
111
|
]);
|
|
112
112
|
|
|
113
113
|
expect(results.filter(Boolean)).toHaveLength(1);
|
|
114
114
|
const stored = await getCallByCallSid(call.callSid);
|
|
115
|
-
expect(stored?.endedBy).toBe(results[0] ? "
|
|
115
|
+
expect(stored?.endedBy).toBe(results[0] ? "user" : "system");
|
|
116
116
|
});
|
|
117
117
|
|
|
118
118
|
it("should return false when the call does not exist", async () => {
|
|
@@ -47,3 +47,10 @@ export const CallStatusValues = {
|
|
|
47
47
|
} as const;
|
|
48
48
|
|
|
49
49
|
export const ALL_CALL_STATUSES = Object.values(CallStatusValues);
|
|
50
|
+
|
|
51
|
+
/** Who ended a connected call: our platform, the bot (end flow), or the caller. */
|
|
52
|
+
export const CallEndedByValues = {
|
|
53
|
+
System: "system",
|
|
54
|
+
Agent: "agent",
|
|
55
|
+
User: "user",
|
|
56
|
+
} as const;
|
|
@@ -3,6 +3,7 @@ import { TranscriptionSegment } from "../results";
|
|
|
3
3
|
import { LeadProperty } from "../leads";
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
|
+
CallEndedByValues,
|
|
6
7
|
CallStatusValues,
|
|
7
8
|
CONFERENCE_ROLE_CUSTOMER,
|
|
8
9
|
CONFERENCE_ROLE_SUPERVISOR,
|
|
@@ -15,7 +16,7 @@ export type ConferenceRole =
|
|
|
15
16
|
|
|
16
17
|
export type CallStatus = (typeof CallStatusValues)[keyof typeof CallStatusValues];
|
|
17
18
|
|
|
18
|
-
export type CallEndedBy =
|
|
19
|
+
export type CallEndedBy = (typeof CallEndedByValues)[keyof typeof CallEndedByValues];
|
|
19
20
|
|
|
20
21
|
export type Call = {
|
|
21
22
|
callSid: string;
|