@tiledesk/tiledesk-server 2.15.1 → 2.15.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/docker-community-profiler-latest.yml +15 -7
- package/.github/workflows/docker-community-push-latest.yml +14 -6
- package/.github/workflows/docker-community-worker-push-latest.yml +15 -8
- package/.github/workflows/docker-image-en-tag-push.yml +8 -5
- package/.github/workflows/docker-image-tag-community-tag-push.yml +22 -5
- package/.github/workflows/docker-image-tag-worker-community-tag-push.yml +22 -7
- package/.github/workflows/docker-push-en-push-latest.yml +19 -10
- package/CHANGELOG.md +10 -0
- package/migrations/1771844588961-phone-channels-migration.js +80 -0
- package/models/contact.js +26 -0
- package/models/request.js +7 -3
- package/package.json +4 -3
- package/routes/message.js +7 -3
- package/routes/request.js +12 -0
- package/services/leadService.js +11 -7
- package/services/requestService.js +5 -3
- package/utils/phoneUtil.js +51 -0
|
@@ -12,12 +12,20 @@ jobs:
|
|
|
12
12
|
runs-on: ubuntu-latest
|
|
13
13
|
|
|
14
14
|
steps:
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
- name: Check out the repo
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Login to Docker Hub
|
|
19
|
+
uses: docker/login-action@v3
|
|
20
|
+
with:
|
|
19
21
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
20
22
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
|
|
24
|
+
- name: Build and push
|
|
25
|
+
uses: docker/build-push-action@v6
|
|
26
|
+
with:
|
|
27
|
+
context: .
|
|
28
|
+
file: ./Dockerfile-profiler
|
|
29
|
+
push: true
|
|
30
|
+
tags: tiledesk/tiledesk-server:latest-profiler
|
|
31
|
+
|
|
@@ -12,11 +12,19 @@ jobs:
|
|
|
12
12
|
runs-on: ubuntu-latest
|
|
13
13
|
|
|
14
14
|
steps:
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
- name: Check out the repo
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Login to Docker Hub
|
|
19
|
+
uses: docker/login-action@v3
|
|
20
|
+
with:
|
|
19
21
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
20
22
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
|
|
24
|
+
- name: Build and push
|
|
25
|
+
uses: docker/build-push-action@v6
|
|
26
|
+
with:
|
|
27
|
+
context: .
|
|
28
|
+
file: ./Dockerfile
|
|
29
|
+
push: true
|
|
30
|
+
tags: tiledesk/tiledesk-server:latest
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
name: Docker Image Community latest CI
|
|
1
|
+
name: Docker Image Community Worker latest CI
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
@@ -12,12 +12,19 @@ jobs:
|
|
|
12
12
|
runs-on: ubuntu-latest
|
|
13
13
|
|
|
14
14
|
steps:
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
- name: Check out the repo
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Login to Docker Hub
|
|
19
|
+
uses: docker/login-action@v3
|
|
20
|
+
with:
|
|
19
21
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
20
22
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
|
|
24
|
+
- name: Build and push
|
|
25
|
+
uses: docker/build-push-action@v6
|
|
26
|
+
with:
|
|
27
|
+
context: .
|
|
28
|
+
file: ./Dockerfile-jobs
|
|
29
|
+
push: true
|
|
30
|
+
tags: tiledesk/tiledesk-server-worker:latest
|
|
@@ -11,15 +11,13 @@ jobs:
|
|
|
11
11
|
runs-on: ubuntu-latest
|
|
12
12
|
steps:
|
|
13
13
|
- name: Check out the repo
|
|
14
|
-
uses: actions/checkout@
|
|
14
|
+
uses: actions/checkout@v4
|
|
15
15
|
|
|
16
16
|
- name: Login to Docker Hub
|
|
17
17
|
uses: docker/login-action@v3
|
|
18
18
|
with:
|
|
19
19
|
username: ${{ secrets.DOCKER_USERNAME }}
|
|
20
20
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
21
|
-
repository: tiledeskrepo/tiledesk-server-enterprise
|
|
22
|
-
tag_with_ref: true
|
|
23
21
|
|
|
24
22
|
- name: Log Voice Token
|
|
25
23
|
run: |
|
|
@@ -29,9 +27,13 @@ jobs:
|
|
|
29
27
|
run: |
|
|
30
28
|
echo "Voice Twilio token log: ${{ secrets.VOICE_TWILIO_TOKEN }}"
|
|
31
29
|
|
|
30
|
+
- name: Log Voice Enghouse Token
|
|
31
|
+
run: |
|
|
32
|
+
echo "Voice Enghouse token log: ${{ secrets.VOICE_ENGHOUSE_TOKEN }}"
|
|
33
|
+
|
|
32
34
|
- name: Generate Docker metadata
|
|
33
35
|
id: meta
|
|
34
|
-
uses: docker/metadata-action@
|
|
36
|
+
uses: docker/metadata-action@v5
|
|
35
37
|
with:
|
|
36
38
|
images: tiledeskrepo/tiledesk-server-enterprise
|
|
37
39
|
tags: |
|
|
@@ -39,7 +41,7 @@ jobs:
|
|
|
39
41
|
type=semver,pattern={{version}}
|
|
40
42
|
|
|
41
43
|
- name: Push to Docker Hub
|
|
42
|
-
uses: docker/build-push-action@
|
|
44
|
+
uses: docker/build-push-action@v6
|
|
43
45
|
with:
|
|
44
46
|
context: .
|
|
45
47
|
file: ./Dockerfile-en
|
|
@@ -48,4 +50,5 @@ jobs:
|
|
|
48
50
|
NPM_TOKEN=${{ secrets.NPM_TOKEN }}
|
|
49
51
|
VOICE_TOKEN=${{ secrets.VOICE_TOKEN }}
|
|
50
52
|
VOICE_TWILIO_TOKEN=${{ secrets.VOICE_TWILIO_TOKEN }}
|
|
53
|
+
VOICE_ENGHOUSE_TOKEN=${{ secrets.VOICE_ENGHOUSE_TOKEN }}
|
|
51
54
|
tags: ${{ steps.meta.outputs.tags }}
|
|
@@ -11,11 +11,28 @@ jobs:
|
|
|
11
11
|
runs-on: ubuntu-latest
|
|
12
12
|
steps:
|
|
13
13
|
- name: Check out the repo
|
|
14
|
-
uses: actions/checkout@
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Login to Docker Hub
|
|
17
|
+
uses: docker/login-action@v3
|
|
17
18
|
with:
|
|
18
19
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
19
20
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
|
|
22
|
+
- name: Generate Docker metadata
|
|
23
|
+
id: meta
|
|
24
|
+
uses: docker/metadata-action@v5
|
|
25
|
+
with:
|
|
26
|
+
images: tiledesk/tiledesk-server
|
|
27
|
+
tags: |
|
|
28
|
+
type=ref,event=branch
|
|
29
|
+
type=semver,pattern={{version}}
|
|
30
|
+
|
|
31
|
+
- name: Build and push
|
|
32
|
+
uses: docker/build-push-action@v6
|
|
33
|
+
with:
|
|
34
|
+
context: .
|
|
35
|
+
file: ./Dockerfile
|
|
36
|
+
push: true
|
|
37
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
38
|
+
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
name: Publish Docker Community image tags
|
|
1
|
+
name: Publish Docker Community Worker image tags
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
@@ -11,12 +11,27 @@ jobs:
|
|
|
11
11
|
runs-on: ubuntu-latest
|
|
12
12
|
steps:
|
|
13
13
|
- name: Check out the repo
|
|
14
|
-
uses: actions/checkout@
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Login to Docker Hub
|
|
17
|
+
uses: docker/login-action@v3
|
|
17
18
|
with:
|
|
18
19
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
19
20
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
|
|
22
|
+
- name: Generate Docker metadata
|
|
23
|
+
id: meta
|
|
24
|
+
uses: docker/metadata-action@v5
|
|
25
|
+
with:
|
|
26
|
+
images: tiledesk/tiledesk-server-worker
|
|
27
|
+
tags: |
|
|
28
|
+
type=ref,event=branch
|
|
29
|
+
type=semver,pattern={{version}}
|
|
30
|
+
|
|
31
|
+
- name: Build and push
|
|
32
|
+
uses: docker/build-push-action@v6
|
|
33
|
+
with:
|
|
34
|
+
context: .
|
|
35
|
+
file: ./Dockerfile-jobs
|
|
36
|
+
push: true
|
|
37
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
@@ -12,13 +12,22 @@ jobs:
|
|
|
12
12
|
runs-on: ubuntu-latest
|
|
13
13
|
|
|
14
14
|
steps:
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
- name: Check out the repo
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Login to Docker Hub
|
|
19
|
+
uses: docker/login-action@v3
|
|
20
|
+
with:
|
|
21
|
+
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
22
|
+
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
23
|
+
|
|
24
|
+
- name: Build and push
|
|
25
|
+
uses: docker/build-push-action@v6
|
|
26
|
+
with:
|
|
27
|
+
context: .
|
|
28
|
+
file: ./Dockerfile-en
|
|
29
|
+
push: true
|
|
30
|
+
build-args: |
|
|
31
|
+
NPM_TOKEN=${{ secrets.NPM_TOKEN }}
|
|
32
|
+
tags: tiledeskrepo/tiledesk-server-enterprise
|
|
33
|
+
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@
|
|
|
5
5
|
🚀 IN PRODUCTION 🚀
|
|
6
6
|
(https://www.npmjs.com/package/@tiledesk/tiledesk-server/v/2.3.77)
|
|
7
7
|
|
|
8
|
+
# 2.15.3
|
|
9
|
+
- Updated whatsapp-connector to 1.0.25
|
|
10
|
+
- Updated sms-connector to 0.1.13
|
|
11
|
+
- Bug fix: join a conversation with a note without text.
|
|
12
|
+
- Added phone number filter when searching for conversations in history
|
|
13
|
+
- Added migration script to add the contact field in request object improving the search by phone number
|
|
14
|
+
|
|
15
|
+
# 2.15.2
|
|
16
|
+
- Updated GitHub actions
|
|
17
|
+
|
|
8
18
|
# 2.15.1
|
|
9
19
|
- Updated whatsapp-connector to 1.0.24
|
|
10
20
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
var winston = require('../config/winston');
|
|
2
|
+
const Request = require('../models/request');
|
|
3
|
+
const phoneUtil = require('../utils/phoneUtil');
|
|
4
|
+
|
|
5
|
+
const VOICE_CHANNEL_NAMES = ['voice_twilio', 'voice-twilio', 'voice-vxml', 'voice-vxml-enghouse'];
|
|
6
|
+
const BATCH_SIZE = 100;
|
|
7
|
+
|
|
8
|
+
async function updateManyWithNormalizedPhone(filter, getPhoneFromDoc) {
|
|
9
|
+
let matched = 0;
|
|
10
|
+
let modified = 0;
|
|
11
|
+
const cursor = Request.find(filter).select('_id attributes createdBy').lean().cursor();
|
|
12
|
+
let batch = [];
|
|
13
|
+
for await (const doc of cursor) {
|
|
14
|
+
const rawPhone = getPhoneFromDoc(doc);
|
|
15
|
+
if (rawPhone == null || String(rawPhone).trim() === '') continue;
|
|
16
|
+
matched++;
|
|
17
|
+
const normalized = phoneUtil.normalizePhone(rawPhone);
|
|
18
|
+
const value = normalized != null && normalized !== '' ? normalized : String(rawPhone).trim();
|
|
19
|
+
if (value === '') continue;
|
|
20
|
+
batch.push({
|
|
21
|
+
updateOne: {
|
|
22
|
+
filter: { _id: doc._id },
|
|
23
|
+
update: { $set: { 'contact.phone': value } }
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
if (batch.length >= BATCH_SIZE) {
|
|
27
|
+
const result = await Request.bulkWrite(batch);
|
|
28
|
+
modified += result.modifiedCount;
|
|
29
|
+
batch = [];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (batch.length > 0) {
|
|
33
|
+
const result = await Request.bulkWrite(batch);
|
|
34
|
+
modified += result.modifiedCount;
|
|
35
|
+
}
|
|
36
|
+
return { matched, modified };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function up() {
|
|
40
|
+
try {
|
|
41
|
+
// Voice channels
|
|
42
|
+
const voiceFilter = {
|
|
43
|
+
'channel.name': { $in: VOICE_CHANNEL_NAMES },
|
|
44
|
+
'attributes.caller_phone': { $exists: true, $nin: [null, ''] }
|
|
45
|
+
};
|
|
46
|
+
const voiceResult = await updateManyWithNormalizedPhone(voiceFilter, (doc) => doc.attributes?.caller_phone);
|
|
47
|
+
winston.info(
|
|
48
|
+
`[phone-channels-migration] Voice channels: matched ${voiceResult.matched}, modified ${voiceResult.modified}`
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// WhatsApp
|
|
52
|
+
const wabFilter = {
|
|
53
|
+
'channel.name': 'whatsapp',
|
|
54
|
+
createdBy: { $regex: /^wab-/ }
|
|
55
|
+
};
|
|
56
|
+
const wabResult = await updateManyWithNormalizedPhone(wabFilter, (doc) =>
|
|
57
|
+
doc.createdBy && doc.createdBy.startsWith('wab-') ? doc.createdBy.replace(/^wab-/, '') : null
|
|
58
|
+
);
|
|
59
|
+
winston.info(
|
|
60
|
+
`[phone-channels-migration] WhatsApp: matched ${wabResult.matched}, modified ${wabResult.modified}`
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// SMS-Twilio
|
|
64
|
+
const smsFilter = {
|
|
65
|
+
'channel.name': 'sms-twilio',
|
|
66
|
+
createdBy: { $regex: /^sms-twilio-/ }
|
|
67
|
+
};
|
|
68
|
+
const smsResult = await updateManyWithNormalizedPhone(smsFilter, (doc) =>
|
|
69
|
+
doc.createdBy && doc.createdBy.startsWith('sms-twilio-') ? doc.createdBy.replace(/^sms-twilio-/, '') : null
|
|
70
|
+
);
|
|
71
|
+
winston.info(
|
|
72
|
+
`[phone-channels-migration] SMS-Twilio: matched ${smsResult.matched}, modified ${smsResult.modified}`
|
|
73
|
+
);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
winston.error('[phone-channels-migration] Error:', err);
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = { up };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const mongoose = require('mongoose');
|
|
2
|
+
const Schema = mongoose.Schema;
|
|
3
|
+
|
|
4
|
+
const ContactSchema = new Schema({
|
|
5
|
+
phone: {
|
|
6
|
+
type: String,
|
|
7
|
+
required: false,
|
|
8
|
+
trim: true
|
|
9
|
+
},
|
|
10
|
+
email: {
|
|
11
|
+
type: String,
|
|
12
|
+
required: false,
|
|
13
|
+
trim: true,
|
|
14
|
+
lowercase: true
|
|
15
|
+
},
|
|
16
|
+
external_id: {
|
|
17
|
+
type: String,
|
|
18
|
+
required: false,
|
|
19
|
+
trim: true
|
|
20
|
+
}
|
|
21
|
+
}, {
|
|
22
|
+
timestamps: false,
|
|
23
|
+
_id: false
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
module.exports = ContactSchema;
|
package/models/request.js
CHANGED
|
@@ -14,7 +14,7 @@ var NoteSchema = require("../models/note").schema;
|
|
|
14
14
|
var TagSchema = require("../models/tag");
|
|
15
15
|
var LocationSchema = require("../models/location");
|
|
16
16
|
var RequestSnapshotSchema = require("../models/requestSnapshot");
|
|
17
|
-
|
|
17
|
+
var ContactSchema = require("../models/contact");
|
|
18
18
|
var defaultFullTextLanguage = process.env.DEFAULT_FULLTEXT_INDEX_LANGUAGE || "none";
|
|
19
19
|
winston.info("Request defaultFullTextLanguage: "+ defaultFullTextLanguage);
|
|
20
20
|
|
|
@@ -304,8 +304,8 @@ var RequestSchema = new Schema({
|
|
|
304
304
|
type: Boolean,
|
|
305
305
|
required: false,
|
|
306
306
|
index: true
|
|
307
|
-
}
|
|
308
|
-
|
|
307
|
+
},
|
|
308
|
+
contact: ContactSchema,
|
|
309
309
|
}, {
|
|
310
310
|
timestamps: true,
|
|
311
311
|
toObject: { virtuals: true }, //IMPORTANT FOR trigger used to polulate messages in toJSON// https://mongoosejs.com/docs/populate.html
|
|
@@ -508,6 +508,10 @@ RequestSchema.index({ id_project: 1, preflight: 1, smartAssignment: 1, "snapshot
|
|
|
508
508
|
|
|
509
509
|
RequestSchema.index({ status: 1, hasBot: 1, updatedAt: 1 }) // For closing unresponsive requests
|
|
510
510
|
|
|
511
|
+
// Contact search by phone / email
|
|
512
|
+
RequestSchema.index({ id_project: 1, 'contact.phone': 1 });
|
|
513
|
+
RequestSchema.index({ id_project: 1, 'contact.email': 1 });
|
|
514
|
+
|
|
511
515
|
// ERROR DURING DEPLOY OF 2.10.27
|
|
512
516
|
//RequestSchema.index({ id_project: 1, participants: 1, "snapshot.agents.id_user": 1, createdAt: -1, status: 1 })
|
|
513
517
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiledesk/tiledesk-server",
|
|
3
3
|
"description": "The Tiledesk server module",
|
|
4
|
-
"version": "2.15.
|
|
4
|
+
"version": "2.15.3",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"start": "node ./bin/www",
|
|
7
7
|
"pretest": "mongodb-runner start",
|
|
@@ -47,12 +47,12 @@
|
|
|
47
47
|
"@tiledesk/tiledesk-messenger-connector": "^0.1.28",
|
|
48
48
|
"@tiledesk/tiledesk-multi-worker": "^0.3.3",
|
|
49
49
|
"@tiledesk/tiledesk-rasa-connector": "^1.0.10",
|
|
50
|
-
"@tiledesk/tiledesk-sms-connector": "^0.1.
|
|
50
|
+
"@tiledesk/tiledesk-sms-connector": "^0.1.13",
|
|
51
51
|
"@tiledesk/tiledesk-telegram-connector": "^0.1.14",
|
|
52
52
|
"@tiledesk/tiledesk-tybot-connector": "^2.0.44",
|
|
53
53
|
"@tiledesk/tiledesk-voice-twilio-connector": "^0.1.28",
|
|
54
54
|
"@tiledesk/tiledesk-vxml-connector": "^0.1.89",
|
|
55
|
-
"@tiledesk/tiledesk-whatsapp-connector": "1.0.
|
|
55
|
+
"@tiledesk/tiledesk-whatsapp-connector": "1.0.25",
|
|
56
56
|
"@tiledesk/tiledesk-whatsapp-jobworker": "^0.0.13",
|
|
57
57
|
"amqplib": "^0.5.5",
|
|
58
58
|
"app-root-path": "^3.0.0",
|
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
"jobs-worker-queued": "^0.0.5",
|
|
86
86
|
"jsdom": "^26.1.0",
|
|
87
87
|
"jsonwebtoken": "^8.5.1",
|
|
88
|
+
"libphonenumber-js": "^1.12.10",
|
|
88
89
|
"lodash": "^4.17.21",
|
|
89
90
|
"marked": "^3.0.4",
|
|
90
91
|
"maskdata": "^1.1.10",
|
package/routes/message.js
CHANGED
|
@@ -149,15 +149,19 @@ async (req, res) => {
|
|
|
149
149
|
// prende fullname e email da quello loggato
|
|
150
150
|
|
|
151
151
|
// createIfNotExistsWithLeadId(lead_id, fullname, email, id_project, createdBy, attributes) {
|
|
152
|
-
return leadService.createIfNotExistsWithLeadId(sender || req.user._id, fullname, email, req.projectid, null, req.body.attributes || req.user.attributes)
|
|
152
|
+
return leadService.createIfNotExistsWithLeadId(sender || req.user._id, fullname, email, req.projectid, null, req.body.attributes || req.user.attributes, undefined, req.user.phone)
|
|
153
153
|
.then(function(createdLead) {
|
|
154
154
|
|
|
155
|
-
|
|
155
|
+
const contact = {
|
|
156
|
+
...(createdLead.phone && { phone: createdLead.phone }),
|
|
157
|
+
...(createdLead.email && { email: createdLead.email })
|
|
158
|
+
};
|
|
156
159
|
|
|
157
160
|
var new_request = {
|
|
158
161
|
request_id: req.params.request_id,
|
|
159
|
-
project_user_id: project_user._id,
|
|
162
|
+
project_user_id: project_user._id,
|
|
160
163
|
lead_id: createdLead._id,
|
|
164
|
+
...(Object.keys(contact).length && { contact }),
|
|
161
165
|
id_project:req.projectid,
|
|
162
166
|
first_text: req.body.text,
|
|
163
167
|
departmentid: req.body.departmentid,
|
package/routes/request.js
CHANGED
|
@@ -753,6 +753,10 @@ router.post('/:requestid/notes', async function (req, res) {
|
|
|
753
753
|
note.text = req.body.text;
|
|
754
754
|
note.createdBy = req.user.id;
|
|
755
755
|
|
|
756
|
+
if (!note.text || note.text.trim() === '') {
|
|
757
|
+
return res.status(400).send({ success: false, error: "Field 'text' is required. Received value: " + note.text });
|
|
758
|
+
}
|
|
759
|
+
|
|
756
760
|
let project_user = req.projectuser;
|
|
757
761
|
|
|
758
762
|
if (project_user.role === RoleConstants.AGENT) {
|
|
@@ -1221,6 +1225,14 @@ router.get('/', function (req, res, next) {
|
|
|
1221
1225
|
query.$text = { "$search": req.query.full_text };
|
|
1222
1226
|
}
|
|
1223
1227
|
|
|
1228
|
+
if (req.query.phone) {
|
|
1229
|
+
// Match by digit sequence so e.g. "3456677888" finds "+393456677888"
|
|
1230
|
+
var phoneDigits = req.query.phone.replace(/\D/g, '');
|
|
1231
|
+
if (phoneDigits.length > 0) {
|
|
1232
|
+
query["contact.phone"] = new RegExp(phoneDigits);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1224
1236
|
var history_search = false;
|
|
1225
1237
|
|
|
1226
1238
|
// Multiple status management
|
package/services/leadService.js
CHANGED
|
@@ -6,6 +6,7 @@ const leadEvent = require('../event/leadEvent');
|
|
|
6
6
|
var winston = require('../config/winston');
|
|
7
7
|
var cacheUtil = require('../utils/cacheUtil');
|
|
8
8
|
var cacheEnabler = require("../services/cacheEnabler");
|
|
9
|
+
var phoneUtil = require('../utils/phoneUtil');
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class LeadService {
|
|
@@ -55,7 +56,7 @@ class LeadService {
|
|
|
55
56
|
|
|
56
57
|
|
|
57
58
|
|
|
58
|
-
createIfNotExistsWithLeadId(lead_id, fullname, email, id_project, createdBy, attributes, status) {
|
|
59
|
+
createIfNotExistsWithLeadId(lead_id, fullname, email, id_project, createdBy, attributes, status, phone) {
|
|
59
60
|
var that = this;
|
|
60
61
|
return new Promise(function (resolve, reject) {
|
|
61
62
|
return Lead.findOne({lead_id: lead_id, id_project: id_project})
|
|
@@ -63,10 +64,10 @@ class LeadService {
|
|
|
63
64
|
.exec(function(err, lead) {
|
|
64
65
|
if (err) {
|
|
65
66
|
winston.error("Error createIfNotExistsWithLeadId", err);
|
|
66
|
-
return resolve(that.createWitId(lead_id, fullname, email, id_project, createdBy, attributes, status));
|
|
67
|
+
return resolve(that.createWitId(lead_id, fullname, email, id_project, createdBy, attributes, status, phone));
|
|
67
68
|
}
|
|
68
69
|
if (!lead) {
|
|
69
|
-
return resolve(that.createWitId(lead_id, fullname, email, id_project, createdBy, attributes, status));
|
|
70
|
+
return resolve(that.createWitId(lead_id, fullname, email, id_project, createdBy, attributes, status, phone));
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
winston.debug("lead.email: " + lead.email);
|
|
@@ -77,7 +78,7 @@ class LeadService {
|
|
|
77
78
|
return resolve(lead);
|
|
78
79
|
} else {
|
|
79
80
|
winston.debug("lead already exists createIfNotExistsWithLeadId but with different email");
|
|
80
|
-
return resolve(that.updateWitId(lead_id, fullname, email, id_project, status));
|
|
81
|
+
return resolve(that.updateWitId(lead_id, fullname, email, id_project, status, phone));
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
|
|
@@ -111,7 +112,7 @@ class LeadService {
|
|
|
111
112
|
});
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
updateWitId(lead_id, fullname, email, id_project, status) {
|
|
115
|
+
updateWitId(lead_id, fullname, email, id_project, status, phone) {
|
|
115
116
|
winston.debug("updateWitId lead_id: "+ lead_id);
|
|
116
117
|
winston.debug("fullname: "+ fullname);
|
|
117
118
|
winston.debug("email: "+ email);
|
|
@@ -124,7 +125,9 @@ class LeadService {
|
|
|
124
125
|
|
|
125
126
|
update.fullname = fullname;
|
|
126
127
|
update.email = email;
|
|
127
|
-
|
|
128
|
+
if (phone !== undefined) {
|
|
129
|
+
update.phone = phoneUtil.normalizePhone(phone);
|
|
130
|
+
}
|
|
128
131
|
if (status) {
|
|
129
132
|
update.status = status;
|
|
130
133
|
}
|
|
@@ -147,7 +150,7 @@ class LeadService {
|
|
|
147
150
|
});
|
|
148
151
|
}
|
|
149
152
|
|
|
150
|
-
createWitId(lead_id, fullname, email, id_project, createdBy, attributes, status) {
|
|
153
|
+
createWitId(lead_id, fullname, email, id_project, createdBy, attributes, status, phone) {
|
|
151
154
|
|
|
152
155
|
if (!createdBy) {
|
|
153
156
|
createdBy = "system";
|
|
@@ -163,6 +166,7 @@ class LeadService {
|
|
|
163
166
|
lead_id: lead_id,
|
|
164
167
|
fullname: fullname,
|
|
165
168
|
email: email,
|
|
169
|
+
phone: phoneUtil.normalizePhone(phone),
|
|
166
170
|
attributes: attributes,
|
|
167
171
|
status: status,
|
|
168
172
|
id_project: id_project,
|
|
@@ -485,7 +485,8 @@ class RequestService {
|
|
|
485
485
|
notes,
|
|
486
486
|
priority,
|
|
487
487
|
auto_close,
|
|
488
|
-
followers
|
|
488
|
+
followers,
|
|
489
|
+
contact
|
|
489
490
|
} = request;
|
|
490
491
|
|
|
491
492
|
let departmentid = request.departmentid || 'default';
|
|
@@ -502,7 +503,7 @@ class RequestService {
|
|
|
502
503
|
request_id, project_user_id, lead_id, id_project,
|
|
503
504
|
first_text, departmentid, sourcePage, language, userAgent, status,
|
|
504
505
|
createdBy, attributes, subject, preflight, channel, location,
|
|
505
|
-
participants,tags,notes,priority,auto_close,followers
|
|
506
|
+
participants,tags,notes,priority,auto_close,followers,contact
|
|
506
507
|
}
|
|
507
508
|
};
|
|
508
509
|
|
|
@@ -648,7 +649,8 @@ class RequestService {
|
|
|
648
649
|
auto_close,
|
|
649
650
|
followers,
|
|
650
651
|
createdAt,
|
|
651
|
-
snapshot
|
|
652
|
+
snapshot,
|
|
653
|
+
contact,
|
|
652
654
|
})
|
|
653
655
|
|
|
654
656
|
if (isTestConversation) {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
const { parsePhoneNumberFromString } = require('libphonenumber-js');
|
|
5
|
+
|
|
6
|
+
function normalizePhone(phone) {
|
|
7
|
+
if (phone == null) return null;
|
|
8
|
+
|
|
9
|
+
if (typeof phone !== 'string') {
|
|
10
|
+
phone = String(phone);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let trimmed = phone.trim();
|
|
14
|
+
if (!trimmed) return null;
|
|
15
|
+
|
|
16
|
+
// Convert 00 to +
|
|
17
|
+
if (trimmed.startsWith('00')) {
|
|
18
|
+
trimmed = '+' + trimmed.slice(2);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Remove spaces and strange characters (keep + initial)
|
|
22
|
+
trimmed = trimmed.replace(/[^\d+]/g, '');
|
|
23
|
+
|
|
24
|
+
// If it starts with +
|
|
25
|
+
if (trimmed.startsWith('+')) {
|
|
26
|
+
const parsed = parsePhoneNumberFromString(trimmed);
|
|
27
|
+
|
|
28
|
+
if (parsed && parsed.isValid()) {
|
|
29
|
+
return parsed.number; // Correct E.164
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// + present but not valid → remove the +
|
|
33
|
+
return trimmed.replace(/^\+/, '');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Try as international without +
|
|
37
|
+
const parsedIntl = parsePhoneNumberFromString('+' + trimmed);
|
|
38
|
+
|
|
39
|
+
if (parsedIntl && parsedIntl.isValid()) {
|
|
40
|
+
return parsedIntl.number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Locale → return only digits
|
|
44
|
+
const digitsOnly = trimmed.replace(/\D/g, '');
|
|
45
|
+
return digitsOnly || null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
module.exports = {
|
|
50
|
+
normalizePhone: normalizePhone
|
|
51
|
+
};
|