mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-23 03:22:19 +03:00
Fix release notes and new csv import (#2116)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
1091ced274
commit
af0b0f3ec2
@ -1,6 +1,8 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 0.6.28 (upcoming)
|
## 0.6.29 (upcoming)
|
||||||
|
|
||||||
|
## 0.6.28
|
||||||
|
|
||||||
Core:
|
Core:
|
||||||
|
|
||||||
@ -17,6 +19,7 @@ Lead:
|
|||||||
- Lead presentation changed to number.
|
- Lead presentation changed to number.
|
||||||
- Title column for leads.
|
- Title column for leads.
|
||||||
- Fix New Lead action for Organization.
|
- Fix New Lead action for Organization.
|
||||||
|
- Duplicate Organization detection
|
||||||
|
|
||||||
## 0.6.27
|
## 0.6.27
|
||||||
|
|
||||||
|
@ -205,6 +205,7 @@ specifiers:
|
|||||||
'@types/compression': ~1.7.2
|
'@types/compression': ~1.7.2
|
||||||
'@types/cors': ^2.8.12
|
'@types/cors': ^2.8.12
|
||||||
'@types/deep-equal': ^1.0.1
|
'@types/deep-equal': ^1.0.1
|
||||||
|
'@types/email-addresses': ^3.0.0
|
||||||
'@types/express': ^4.17.13
|
'@types/express': ^4.17.13
|
||||||
'@types/express-fileupload': ^1.1.7
|
'@types/express-fileupload': ^1.1.7
|
||||||
'@types/faker': ~5.5.9
|
'@types/faker': ~5.5.9
|
||||||
@ -241,6 +242,7 @@ specifiers:
|
|||||||
dotenv: ~16.0.0
|
dotenv: ~16.0.0
|
||||||
dotenv-webpack: ^7.0.2
|
dotenv-webpack: ^7.0.2
|
||||||
elastic-apm-node: ~3.26.0
|
elastic-apm-node: ~3.26.0
|
||||||
|
email-addresses: ^5.0.0
|
||||||
esbuild: ^0.12.26
|
esbuild: ^0.12.26
|
||||||
eslint: ^7.32.0
|
eslint: ^7.32.0
|
||||||
eslint-config-standard-with-typescript: ^21.0.1
|
eslint-config-standard-with-typescript: ^21.0.1
|
||||||
@ -263,6 +265,7 @@ specifiers:
|
|||||||
koa-bodyparser: ^4.3.0
|
koa-bodyparser: ^4.3.0
|
||||||
koa-router: ^10.1.1
|
koa-router: ^10.1.1
|
||||||
lexorank: ~1.0.4
|
lexorank: ~1.0.4
|
||||||
|
libphonenumber-js: ^1.9.46
|
||||||
mime-types: ~2.1.34
|
mime-types: ~2.1.34
|
||||||
mini-css-extract-plugin: ^2.2.0
|
mini-css-extract-plugin: ^2.2.0
|
||||||
minio: ^7.0.26
|
minio: ^7.0.26
|
||||||
@ -502,6 +505,7 @@ dependencies:
|
|||||||
'@types/compression': 1.7.2
|
'@types/compression': 1.7.2
|
||||||
'@types/cors': 2.8.12
|
'@types/cors': 2.8.12
|
||||||
'@types/deep-equal': 1.0.1
|
'@types/deep-equal': 1.0.1
|
||||||
|
'@types/email-addresses': 3.0.0
|
||||||
'@types/express': 4.17.13
|
'@types/express': 4.17.13
|
||||||
'@types/express-fileupload': 1.2.2
|
'@types/express-fileupload': 1.2.2
|
||||||
'@types/faker': 5.5.9
|
'@types/faker': 5.5.9
|
||||||
@ -538,6 +542,7 @@ dependencies:
|
|||||||
dotenv: 16.0.1
|
dotenv: 16.0.1
|
||||||
dotenv-webpack: 7.1.0_webpack@5.73.0
|
dotenv-webpack: 7.1.0_webpack@5.73.0
|
||||||
elastic-apm-node: 3.26.0
|
elastic-apm-node: 3.26.0
|
||||||
|
email-addresses: 5.0.0
|
||||||
esbuild: 0.12.29
|
esbuild: 0.12.29
|
||||||
eslint: 7.32.0
|
eslint: 7.32.0
|
||||||
eslint-config-standard-with-typescript: 21.0.1_99a5fe2f2ae1dc64d6b59974c931eb2a
|
eslint-config-standard-with-typescript: 21.0.1_99a5fe2f2ae1dc64d6b59974c931eb2a
|
||||||
@ -560,6 +565,7 @@ dependencies:
|
|||||||
koa-bodyparser: 4.3.0
|
koa-bodyparser: 4.3.0
|
||||||
koa-router: 10.1.1
|
koa-router: 10.1.1
|
||||||
lexorank: 1.0.4
|
lexorank: 1.0.4
|
||||||
|
libphonenumber-js: 1.10.6
|
||||||
mime-types: 2.1.35
|
mime-types: 2.1.35
|
||||||
mini-css-extract-plugin: 2.6.0_webpack@5.73.0
|
mini-css-extract-plugin: 2.6.0_webpack@5.73.0
|
||||||
minio: 7.0.28
|
minio: 7.0.28
|
||||||
@ -2223,6 +2229,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-mMUu4nWHLBlHtxXY17Fg6+ucS/MnndyOWyOe7MmwkoMYxvfQU2ajtRaEvqSUv+aVkMqH/C0NCI8UoVfRNQ10yg==}
|
resolution: {integrity: sha512-mMUu4nWHLBlHtxXY17Fg6+ucS/MnndyOWyOe7MmwkoMYxvfQU2ajtRaEvqSUv+aVkMqH/C0NCI8UoVfRNQ10yg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@types/email-addresses/3.0.0:
|
||||||
|
resolution: {integrity: sha512-jGUOSgpOEWhTH4tMCj56NZenkzER259nJ5NGRvxXld3X7Lai/lxC3QNfDM0rVGMkj+WhANMpvIf195tgwnE7wQ==}
|
||||||
|
deprecated: This is a stub types definition for email-addresses (https://github.com/jackbowman/email-addresses). email-addresses provides its own type definitions, so you don't need @types/email-addresses installed!
|
||||||
|
dependencies:
|
||||||
|
email-addresses: 5.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/eslint-scope/3.7.3:
|
/@types/eslint-scope/3.7.3:
|
||||||
resolution: {integrity: sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==}
|
resolution: {integrity: sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -4463,6 +4476,10 @@ packages:
|
|||||||
minimalistic-crypto-utils: 1.0.1
|
minimalistic-crypto-utils: 1.0.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/email-addresses/5.0.0:
|
||||||
|
resolution: {integrity: sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/emittery/0.8.1:
|
/emittery/0.8.1:
|
||||||
resolution: {integrity: sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==}
|
resolution: {integrity: sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@ -6879,6 +6896,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-CMgA8AMJIX/QfoYHKyjg0hv9W1SGL2xRkt0uLyhT9xKKRj73fHi+IhsrB3W36wwk4I0iz8YlKHfdW14QDwerMA==}
|
resolution: {integrity: sha512-CMgA8AMJIX/QfoYHKyjg0hv9W1SGL2xRkt0uLyhT9xKKRj73fHi+IhsrB3W36wwk4I0iz8YlKHfdW14QDwerMA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/libphonenumber-js/1.10.6:
|
||||||
|
resolution: {integrity: sha512-CIjT100/SmntsUjsLVs2t3ufeN4KdNXUxhD07tH153pdbaCWuAjv0jK/gPuywR3IImB/U/MQM+x9RfhMs5XZiA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/lilconfig/2.0.5:
|
/lilconfig/2.0.5:
|
||||||
resolution: {integrity: sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==}
|
resolution: {integrity: sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@ -10964,7 +10985,7 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/hr-resources.tgz_1e3963ebf0ceeb25b2fa6a1cc87e253c:
|
file:projects/hr-resources.tgz_1e3963ebf0ceeb25b2fa6a1cc87e253c:
|
||||||
resolution: {integrity: sha512-sYnkYAs2h/gH6VD8c6+QBHMemRZfIMVthpYzi1+TXliv/EmebzxdFyPCPjLbxgQxpq4mUwuMLlAusMIrmpVNrg==, tarball: file:projects/hr-resources.tgz}
|
resolution: {integrity: sha512-XEARExHSGYAtE9S/t40BikzGTpOvffiQ07PdMv7XooZao5H2Rq9INzFCfe78vORfpmR0qaR8EhxBHyyJQbJ5ng==, tarball: file:projects/hr-resources.tgz}
|
||||||
id: file:projects/hr-resources.tgz
|
id: file:projects/hr-resources.tgz
|
||||||
name: '@rush-temp/hr-resources'
|
name: '@rush-temp/hr-resources'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
@ -11626,7 +11647,7 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/model-hr.tgz_typescript@4.7.2:
|
file:projects/model-hr.tgz_typescript@4.7.2:
|
||||||
resolution: {integrity: sha512-HtAgUigvoyvjSpGUHwcRfTqM+RpMKyvDrieHpRBIvFans/2Kg3FPlacTHa9vhBilcze0pNBDrlSXyGuKMBLl8g==, tarball: file:projects/model-hr.tgz}
|
resolution: {integrity: sha512-N+3GtMbOYJZCiiIrw5lwGQ9tkHgePYpxLRtnZdCN15CkFY1hbFx55NeTDhp5rMq09FeWjxuBSSuXJyWu96rIMg==, tarball: file:projects/model-hr.tgz}
|
||||||
id: file:projects/model-hr.tgz
|
id: file:projects/model-hr.tgz
|
||||||
name: '@rush-temp/model-hr'
|
name: '@rush-temp/model-hr'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
@ -14440,12 +14461,13 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/tool.tgz:
|
file:projects/tool.tgz:
|
||||||
resolution: {integrity: sha512-gvdRnHRhzst5gkHEFg9GVc3D04b+BAks8qPdrVxHQSWvWiiUmh3kG+uNWQKklSa9rtRzlSHUrf6NvUvgEg2c5w==, tarball: file:projects/tool.tgz}
|
resolution: {integrity: sha512-JO6pn5WreSM6pO8MAbVM0lDk6nYKwfX7fLjfpcBv8AQ2C0ZHuGxbFGOA75Y1ccsq3fB4n35KMgBCUKnecCBZKQ==, tarball: file:projects/tool.tgz}
|
||||||
name: '@rush-temp/tool'
|
name: '@rush-temp/tool'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@elastic/elasticsearch': 7.17.0
|
'@elastic/elasticsearch': 7.17.0
|
||||||
'@rushstack/heft': 0.45.5
|
'@rushstack/heft': 0.45.5
|
||||||
|
'@types/email-addresses': 3.0.0
|
||||||
'@types/heft-jest': 1.0.2
|
'@types/heft-jest': 1.0.2
|
||||||
'@types/mime-types': 2.1.1
|
'@types/mime-types': 2.1.1
|
||||||
'@types/minio': 7.0.13
|
'@types/minio': 7.0.13
|
||||||
@ -14458,6 +14480,7 @@ packages:
|
|||||||
commander: 8.3.0
|
commander: 8.3.0
|
||||||
cross-env: 7.0.3
|
cross-env: 7.0.3
|
||||||
csv-parse: 5.1.0
|
csv-parse: 5.1.0
|
||||||
|
email-addresses: 5.0.0
|
||||||
esbuild: 0.12.29
|
esbuild: 0.12.29
|
||||||
eslint: 7.32.0
|
eslint: 7.32.0
|
||||||
eslint-config-standard-with-typescript: 21.0.1_99a5fe2f2ae1dc64d6b59974c931eb2a
|
eslint-config-standard-with-typescript: 21.0.1_99a5fe2f2ae1dc64d6b59974c931eb2a
|
||||||
@ -14466,6 +14489,7 @@ packages:
|
|||||||
eslint-plugin-promise: 5.2.0_eslint@7.32.0
|
eslint-plugin-promise: 5.2.0_eslint@7.32.0
|
||||||
fast-equals: 2.0.4
|
fast-equals: 2.0.4
|
||||||
got: 11.8.5
|
got: 11.8.5
|
||||||
|
libphonenumber-js: 1.10.6
|
||||||
mime-types: 2.1.35
|
mime-types: 2.1.35
|
||||||
minio: 7.0.28
|
minio: 7.0.28
|
||||||
mongodb: 4.6.0
|
mongodb: 4.6.0
|
||||||
|
@ -39,7 +39,8 @@
|
|||||||
"@types/ws": "^8.2.1",
|
"@types/ws": "^8.2.1",
|
||||||
"@types/xml2js": "~0.4.9",
|
"@types/xml2js": "~0.4.9",
|
||||||
"@types/mime-types": "~2.1.1",
|
"@types/mime-types": "~2.1.1",
|
||||||
"@types/request": "~2.48.8"
|
"@types/request": "~2.48.8",
|
||||||
|
"@types/email-addresses": "^3.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mongodb": "^4.1.1",
|
"mongodb": "^4.1.1",
|
||||||
@ -110,6 +111,8 @@
|
|||||||
"@anticrm/tags": "~0.6.2",
|
"@anticrm/tags": "~0.6.2",
|
||||||
"@anticrm/server-backup": "~0.6.0",
|
"@anticrm/server-backup": "~0.6.0",
|
||||||
"csv-parse": "~5.1.0",
|
"csv-parse": "~5.1.0",
|
||||||
"@anticrm/lead": "~0.6.0"
|
"@anticrm/lead": "~0.6.0",
|
||||||
|
"email-addresses": "^5.0.0",
|
||||||
|
"libphonenumber-js": "^1.9.46"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,9 @@ import { Db, MongoClient } from 'mongodb'
|
|||||||
import { exit } from 'process'
|
import { exit } from 'process'
|
||||||
import { rebuildElastic } from './elastic'
|
import { rebuildElastic } from './elastic'
|
||||||
import { importXml } from './importer'
|
import { importXml } from './importer'
|
||||||
import { importLead } from './lead-importer'
|
import { removeDuplicates } from './leads/duplicates'
|
||||||
|
import { importLead } from './leads/lead-importer'
|
||||||
|
import { importLead2 } from './leads/lead-importer2'
|
||||||
import { updateCandidates } from './recruit'
|
import { updateCandidates } from './recruit'
|
||||||
import { clearTelegramHistory } from './telegram'
|
import { clearTelegramHistory } from './telegram'
|
||||||
import { diffWorkspace, dumpWorkspace, restoreWorkspace } from './workspace'
|
import { diffWorkspace, dumpWorkspace, restoreWorkspace } from './workspace'
|
||||||
@ -315,6 +317,20 @@ program
|
|||||||
return await importLead(transactorUrl, workspace, fileName)
|
return await importLead(transactorUrl, workspace, fileName)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('import-lead-csv2 <workspace> <fileName>')
|
||||||
|
.description('Import LEAD csv customer organizations')
|
||||||
|
.action(async (workspace, fileName, cmd) => {
|
||||||
|
return await importLead2(transactorUrl, workspace, fileName)
|
||||||
|
})
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('lead-duplicates <workspace>')
|
||||||
|
.description('Find and remove duplicate organizations.')
|
||||||
|
.action(async (workspace, cmd) => {
|
||||||
|
return await removeDuplicates(transactorUrl, workspace)
|
||||||
|
})
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('generate-token <name> <workspace>')
|
.command('generate-token <name> <workspace>')
|
||||||
.description('generate token')
|
.description('generate token')
|
||||||
|
92
dev/tool/src/leads/classes.ts
Normal file
92
dev/tool/src/leads/classes.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
//
|
||||||
|
// Copyright © 2022 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import core, { AnyAttribute, Data, Enum, EnumOf, Ref, TxOperations } from '@anticrm/core'
|
||||||
|
import lead from '@anticrm/lead'
|
||||||
|
import { getEmbeddedLabel } from '@anticrm/platform'
|
||||||
|
import { FieldType } from './types'
|
||||||
|
|
||||||
|
export async function updateClasses (
|
||||||
|
client: TxOperations,
|
||||||
|
records: any[],
|
||||||
|
fieldMapping: Record<string, FieldType>
|
||||||
|
): Promise<void> {
|
||||||
|
const allAttrs = client.getHierarchy().getAllAttributes(lead.mixin.Customer)
|
||||||
|
for (const [k, v] of Object.entries(fieldMapping)) {
|
||||||
|
if (v.type === undefined) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let attr = allAttrs.get(v.name)
|
||||||
|
if (attr === undefined) {
|
||||||
|
try {
|
||||||
|
if (!client.getHierarchy().isDerived(v.type, core.class.Type)) {
|
||||||
|
// Skip channels mapping
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} catch (any) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Create attr
|
||||||
|
const data: Data<AnyAttribute> = {
|
||||||
|
attributeOf: lead.mixin.Customer,
|
||||||
|
name: v.name,
|
||||||
|
label: getEmbeddedLabel(k),
|
||||||
|
isCustom: true,
|
||||||
|
type: {
|
||||||
|
_class: v.type,
|
||||||
|
label: v.label ?? core.string.String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (client.getHierarchy().isDerived(v.type, core.class.EnumOf)) {
|
||||||
|
;(data.type as EnumOf).of = `lead:class:${(v as any).enumName as string}` as Ref<Enum>
|
||||||
|
}
|
||||||
|
const attrId = (lead.mixin.Customer + '.' + v.name) as Ref<AnyAttribute>
|
||||||
|
await client.createDoc(core.class.Attribute, core.space.Model, data, attrId)
|
||||||
|
attr = await client.findOne(core.class.Attribute, { _id: attrId })
|
||||||
|
}
|
||||||
|
// Check update Enum/Values
|
||||||
|
if (client.getHierarchy().isDerived(v.type, core.class.EnumOf)) {
|
||||||
|
const enumName = (v as any).enumName as string
|
||||||
|
const enumId = `lead:class:${enumName}` as Ref<Enum>
|
||||||
|
let enumClass = await client.findOne(core.class.Enum, { _id: enumId })
|
||||||
|
if (enumClass === undefined) {
|
||||||
|
await client.createDoc(
|
||||||
|
core.class.Enum,
|
||||||
|
core.space.Model,
|
||||||
|
{
|
||||||
|
name: enumName,
|
||||||
|
enumValues: []
|
||||||
|
},
|
||||||
|
enumId
|
||||||
|
)
|
||||||
|
enumClass = client.getModel().getObject(enumId)
|
||||||
|
}
|
||||||
|
// Check values
|
||||||
|
const mapv = (v?: string): string =>
|
||||||
|
(v?.toString() ?? '').trim().length === 0 ? 'не задано' : (v?.toString() ?? '').trim()
|
||||||
|
const values = records
|
||||||
|
.map((it) => it[v.fName ?? k])
|
||||||
|
.map(mapv)
|
||||||
|
.filter((it, idx, arr) => arr.indexOf(it) === idx)
|
||||||
|
for (const v of values) {
|
||||||
|
if (!enumClass.enumValues.includes(v)) {
|
||||||
|
await client.update(enumClass, {
|
||||||
|
$push: { enumValues: v }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
60
dev/tool/src/leads/duplicates.ts
Normal file
60
dev/tool/src/leads/duplicates.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
//
|
||||||
|
// Copyright © 2022 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import contact, { EmployeeAccount } from '@anticrm/contact'
|
||||||
|
import { Ref, TxOperations } from '@anticrm/core'
|
||||||
|
import { connect } from '@anticrm/server-tool'
|
||||||
|
import { deepEqual } from 'fast-equals'
|
||||||
|
|
||||||
|
export async function removeDuplicates (transactorUrl: string, dbName: string): Promise<void> {
|
||||||
|
const connection = await connect(transactorUrl, dbName)
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('loading cvs document...')
|
||||||
|
|
||||||
|
const client = new TxOperations(connection, 'core:account:lead-importer' as Ref<EmployeeAccount>)
|
||||||
|
|
||||||
|
const organizationNames = await client.findAll(contact.class.Organization, {})
|
||||||
|
|
||||||
|
const unicOrg = organizationNames.filter((it, idx, arr) => idx === arr.findIndex((nit) => it.name === nit.name))
|
||||||
|
let total = 0
|
||||||
|
for (const org of unicOrg) {
|
||||||
|
const sameName = organizationNames.filter((it) => it.name === org.name)
|
||||||
|
|
||||||
|
if (sameName.length > 1) {
|
||||||
|
console.log('duplicate orgs', org.name)
|
||||||
|
total += sameName.length - 1
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = sameName[0]
|
||||||
|
for (const oi of sameName.slice(1)) {
|
||||||
|
const { _id: tid, modifiedOn: _1, ...tdata } = target
|
||||||
|
const { _id: oid, modifiedOn: _2, ...oiddata } = oi
|
||||||
|
if (deepEqual(tdata, oiddata)) {
|
||||||
|
// If same we could remove oid
|
||||||
|
await client.remove(oi)
|
||||||
|
console.log('removed', oid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('Total duplicates', total)
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error(err)
|
||||||
|
} finally {
|
||||||
|
await connection.close()
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
// Copyright © 2022 Hardcore Engineering Inc.
|
||||||
// Copyright © 2021 Hardcore Engineering Inc.
|
|
||||||
//
|
//
|
||||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License. You may
|
// you may not use this file except in compliance with the License. You may
|
||||||
@ -15,41 +14,16 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import contact, { Contact, EmployeeAccount, Organization } from '@anticrm/contact'
|
import contact, { Contact, EmployeeAccount, Organization } from '@anticrm/contact'
|
||||||
import core, {
|
import core, { Class, DocumentUpdate, MixinUpdate, Ref, SortingOrder, TxOperations } from '@anticrm/core'
|
||||||
AnyAttribute,
|
|
||||||
Class,
|
|
||||||
Data,
|
|
||||||
Doc,
|
|
||||||
DocumentUpdate,
|
|
||||||
Enum,
|
|
||||||
EnumOf,
|
|
||||||
MixinUpdate,
|
|
||||||
PropertyType,
|
|
||||||
Ref,
|
|
||||||
SortingOrder,
|
|
||||||
TxOperations,
|
|
||||||
Type
|
|
||||||
} from '@anticrm/core'
|
|
||||||
import lead, { Customer, Funnel, Lead } from '@anticrm/lead'
|
import lead, { Customer, Funnel, Lead } from '@anticrm/lead'
|
||||||
import { getEmbeddedLabel } from '@anticrm/platform'
|
|
||||||
import { connect } from '@anticrm/server-tool'
|
import { connect } from '@anticrm/server-tool'
|
||||||
import task, { calcRank, DoneState, genRanks, State } from '@anticrm/task'
|
import task, { calcRank, DoneState, genRanks, State } from '@anticrm/task'
|
||||||
import { parse } from 'csv-parse'
|
|
||||||
import { readFile } from 'fs/promises'
|
import { readFile } from 'fs/promises'
|
||||||
|
import { updateClasses } from './classes'
|
||||||
|
import { CustomCustomer, FieldType } from './types'
|
||||||
|
import { filled } from './utils'
|
||||||
|
|
||||||
function filled (obj: any, uniqKeys: string[]): any {
|
import { parse } from 'csv-parse'
|
||||||
const result: Record<string, any> = {}
|
|
||||||
for (const [k, v] of Object.entries(obj)) {
|
|
||||||
if (typeof v === 'string' && v.trim().length === 0) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (!uniqKeys.includes(k)) {
|
|
||||||
uniqKeys.push(k)
|
|
||||||
}
|
|
||||||
result[k] = v
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
const names = {
|
const names = {
|
||||||
orgName: 'Название компании',
|
orgName: 'Название компании',
|
||||||
@ -74,19 +48,7 @@ const names = {
|
|||||||
responsible: 'Ответственный'
|
responsible: 'Ответственный'
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CustomCustomer extends Customer {
|
const fieldMapping: Record<string, FieldType> = {
|
||||||
annualTurnover?: string
|
|
||||||
currency?: string
|
|
||||||
comment?: string
|
|
||||||
source_ka: string
|
|
||||||
employees_count?: number
|
|
||||||
activity_area: string
|
|
||||||
company_type: string
|
|
||||||
processing_status: string
|
|
||||||
responsible: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const fieldMapping = {
|
|
||||||
'Название компании': { name: 'name', type: core.class.TypeString, label: core.string.String },
|
'Название компании': { name: 'name', type: core.class.TypeString, label: core.string.String },
|
||||||
'Годовой оборот': { name: 'annual.turnover', type: core.class.TypeNumber, label: core.string.Number },
|
'Годовой оборот': { name: 'annual.turnover', type: core.class.TypeNumber, label: core.string.Number },
|
||||||
Валюта: {
|
Валюта: {
|
||||||
@ -99,7 +61,7 @@ const fieldMapping = {
|
|||||||
'Кем создана': { name: 'created_by', type: undefined, label: core.string.String },
|
'Кем создана': { name: 'created_by', type: undefined, label: core.string.String },
|
||||||
'Кем изменена': { name: 'modified_by', type: undefined, label: core.string.String },
|
'Кем изменена': { name: 'modified_by', type: undefined, label: core.string.String },
|
||||||
'Источник (где связь) для КА': { name: 'source_ka', type: core.class.TypeString, label: core.string.String },
|
'Источник (где связь) для КА': { name: 'source_ka', type: core.class.TypeString, label: core.string.String },
|
||||||
'Корпоративный сайт': { name: 'corporate_site', type: contact.channelProvider.Homepage, label: undefined },
|
'Корпоративный сайт': { name: 'corporate_site', type: undefined, label: undefined },
|
||||||
'Кол-во сотрудников': {
|
'Кол-во сотрудников': {
|
||||||
name: 'employees_count',
|
name: 'employees_count',
|
||||||
type: core.class.EnumOf,
|
type: core.class.EnumOf,
|
||||||
@ -126,7 +88,6 @@ const fieldMapping = {
|
|||||||
},
|
},
|
||||||
'Корпоративный e-mail': {
|
'Корпоративный e-mail': {
|
||||||
name: 'corporate_email',
|
name: 'corporate_email',
|
||||||
type: contact.channelProvider.Email,
|
|
||||||
label: core.string.String
|
label: core.string.String
|
||||||
},
|
},
|
||||||
Ответственный: {
|
Ответственный: {
|
||||||
@ -136,67 +97,7 @@ const fieldMapping = {
|
|||||||
label: core.string.Enum
|
label: core.string.Enum
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function uodateClasses (client: TxOperations, records: any[]): Promise<void> {
|
|
||||||
const allAttrs = client.getHierarchy().getAllAttributes(lead.mixin.Customer)
|
|
||||||
for (const [k, v] of Object.entries(fieldMapping)) {
|
|
||||||
let attr = allAttrs.get(v.name)
|
|
||||||
if (attr === undefined) {
|
|
||||||
try {
|
|
||||||
if (!client.getHierarchy().isDerived(v.type as Ref<Class<Doc>>, core.class.Type)) {
|
|
||||||
// Skip channels mapping
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} catch (any) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Create attr
|
|
||||||
const data: Data<AnyAttribute> = {
|
|
||||||
attributeOf: lead.mixin.Customer,
|
|
||||||
name: v.name,
|
|
||||||
label: getEmbeddedLabel(k),
|
|
||||||
isCustom: true,
|
|
||||||
type: {
|
|
||||||
_class: v.type as Ref<Class<Type<PropertyType>>>,
|
|
||||||
label: v.label ?? core.string.String
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (client.getHierarchy().isDerived(v.type as Ref<Class<Doc>>, core.class.EnumOf)) {
|
|
||||||
;(data.type as EnumOf).of = `lead:class:${(v as any).enumName as string}` as Ref<Enum>
|
|
||||||
}
|
|
||||||
const attrId = (lead.mixin.Customer + '.' + v.name) as Ref<AnyAttribute>
|
|
||||||
await client.createDoc(core.class.Attribute, core.space.Model, data, attrId)
|
|
||||||
attr = await client.findOne(core.class.Attribute, { _id: attrId })
|
|
||||||
}
|
|
||||||
// Check update Enum/Values
|
|
||||||
if (client.getHierarchy().isDerived(v.type as Ref<Class<Doc>>, core.class.EnumOf)) {
|
|
||||||
const enumName = (v as any).enumName as string
|
|
||||||
const enumId = `lead:class:${enumName}` as Ref<Enum>
|
|
||||||
let enumClass = await client.findOne(core.class.Enum, { _id: enumId })
|
|
||||||
if (enumClass === undefined) {
|
|
||||||
await client.createDoc(
|
|
||||||
core.class.Enum,
|
|
||||||
core.space.Model,
|
|
||||||
{
|
|
||||||
name: enumName,
|
|
||||||
enumValues: []
|
|
||||||
},
|
|
||||||
enumId
|
|
||||||
)
|
|
||||||
enumClass = client.getModel().getObject(enumId)
|
|
||||||
}
|
|
||||||
// Check values
|
|
||||||
const values = records.map((it) => it[k]).filter((it, idx, arr) => arr.indexOf(it) === idx)
|
|
||||||
for (const v of values) {
|
|
||||||
const vv = (v ?? '').trim().length === 0 ? 'не задано' : v
|
|
||||||
if (!enumClass.enumValues.includes(vv)) {
|
|
||||||
await client.update(enumClass, {
|
|
||||||
$push: { enumValues: vv }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async function updateStates<T extends State | DoneState> (
|
async function updateStates<T extends State | DoneState> (
|
||||||
client: TxOperations,
|
client: TxOperations,
|
||||||
states: string[],
|
states: string[],
|
||||||
@ -224,6 +125,31 @@ async function updateStates<T extends State | DoneState> (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function parseCSV (csvData: string): Promise<any[]> {
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
parse(
|
||||||
|
csvData,
|
||||||
|
{
|
||||||
|
delimiter: ';',
|
||||||
|
columns: true,
|
||||||
|
quote: '"',
|
||||||
|
bom: true,
|
||||||
|
cast: true,
|
||||||
|
autoParse: true,
|
||||||
|
castDate: false,
|
||||||
|
skipEmptyLines: true,
|
||||||
|
skipRecordsWithEmptyValues: true
|
||||||
|
},
|
||||||
|
(err, records) => {
|
||||||
|
if (err !== undefined) {
|
||||||
|
console.error(err)
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
resolve(records)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
export async function importLead (transactorUrl: string, dbName: string, csvFile: string): Promise<void> {
|
export async function importLead (transactorUrl: string, dbName: string, csvFile: string): Promise<void> {
|
||||||
const connection = await connect(transactorUrl, dbName)
|
const connection = await connect(transactorUrl, dbName)
|
||||||
|
|
||||||
@ -238,7 +164,7 @@ export async function importLead (transactorUrl: string, dbName: string, csvFile
|
|||||||
|
|
||||||
const client = new TxOperations(connection, 'core:account:lead-importer' as Ref<EmployeeAccount>)
|
const client = new TxOperations(connection, 'core:account:lead-importer' as Ref<EmployeeAccount>)
|
||||||
|
|
||||||
await uodateClasses(client, records)
|
await updateClasses(client, records, fieldMapping)
|
||||||
await createCustomers(client, filledFields)
|
await createCustomers(client, filledFields)
|
||||||
|
|
||||||
const importedFunnelId = await createFunnel(records, client)
|
const importedFunnelId = await createFunnel(records, client)
|
||||||
@ -464,29 +390,3 @@ async function createCustomers (client: TxOperations, filledFields: any[]): Prom
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function parseCSV (csvData: string): Promise<any[]> {
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
parse(
|
|
||||||
csvData,
|
|
||||||
{
|
|
||||||
delimiter: ';',
|
|
||||||
columns: true,
|
|
||||||
quote: '"',
|
|
||||||
bom: true,
|
|
||||||
cast: true,
|
|
||||||
autoParse: true,
|
|
||||||
castDate: false,
|
|
||||||
skipEmptyLines: true,
|
|
||||||
skipRecordsWithEmptyValues: true
|
|
||||||
},
|
|
||||||
(err, records) => {
|
|
||||||
if (err !== undefined) {
|
|
||||||
console.error(err)
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
resolve(records)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
267
dev/tool/src/leads/lead-importer2.ts
Normal file
267
dev/tool/src/leads/lead-importer2.ts
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
//
|
||||||
|
// Copyright © 2022 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import contact, { Contact, EmployeeAccount, Organization } from '@anticrm/contact'
|
||||||
|
import core, { generateId, MixinUpdate, Ref, TxOperations } from '@anticrm/core'
|
||||||
|
import lead from '@anticrm/lead'
|
||||||
|
import { connect } from '@anticrm/server-tool'
|
||||||
|
import { readFile } from 'fs/promises'
|
||||||
|
import { updateClasses } from './classes'
|
||||||
|
import { CustomCustomer, FieldType } from './types'
|
||||||
|
import { filled } from './utils'
|
||||||
|
|
||||||
|
import { parse } from 'csv-parse'
|
||||||
|
import parsePhoneNumber from 'libphonenumber-js'
|
||||||
|
import addrs from 'email-addresses'
|
||||||
|
|
||||||
|
const names = {
|
||||||
|
orgName: 'название компании',
|
||||||
|
corporate_site: 'сайт компании',
|
||||||
|
contact_person: 'контактное лицо',
|
||||||
|
contacts: 'контакты',
|
||||||
|
dialog_area: 'место диалога: ССеть, почта, ТГ',
|
||||||
|
sales_manager: 'seles-менеджер',
|
||||||
|
bot_account: 'бот аккаунт',
|
||||||
|
description: 'договорённости, доп. инфа о компаниии, вакансии',
|
||||||
|
hh_link: 'ссылка hh'
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldMapping: Record<string, FieldType> = {
|
||||||
|
'Место диалога': {
|
||||||
|
name: 'dialog_area',
|
||||||
|
type: core.class.EnumOf,
|
||||||
|
enumName: 'CompanyDialogArea',
|
||||||
|
label: core.string.Enum,
|
||||||
|
fName: names.dialog_area
|
||||||
|
},
|
||||||
|
'Sales Manager': {
|
||||||
|
name: 'sales_manager',
|
||||||
|
type: core.class.EnumOf,
|
||||||
|
enumName: 'CompanySalesManager',
|
||||||
|
label: core.string.Enum,
|
||||||
|
fName: names.sales_manager
|
||||||
|
},
|
||||||
|
'Бот Аккаунт': {
|
||||||
|
name: 'bot_account',
|
||||||
|
type: core.class.EnumOf,
|
||||||
|
enumName: 'CompanyBotAccount',
|
||||||
|
label: core.string.Enum,
|
||||||
|
fName: names.bot_account
|
||||||
|
},
|
||||||
|
'Контактное Лицо': {
|
||||||
|
name: 'contact_person',
|
||||||
|
type: core.class.TypeString,
|
||||||
|
label: core.string.String
|
||||||
|
},
|
||||||
|
Контакты: {
|
||||||
|
name: 'contacts',
|
||||||
|
type: core.class.TypeString,
|
||||||
|
label: core.string.String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function parseCSV (csvData: string): Promise<any[]> {
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
parse(
|
||||||
|
csvData,
|
||||||
|
{
|
||||||
|
delimiter: ',',
|
||||||
|
columns: true,
|
||||||
|
quote: '"',
|
||||||
|
bom: true,
|
||||||
|
cast: true,
|
||||||
|
autoParse: true,
|
||||||
|
castDate: false,
|
||||||
|
skipEmptyLines: true,
|
||||||
|
skipRecordsWithEmptyValues: true
|
||||||
|
},
|
||||||
|
(err, records) => {
|
||||||
|
if (err !== undefined) {
|
||||||
|
console.error(err)
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
resolve(records)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function importLead2 (transactorUrl: string, dbName: string, csvFile: string): Promise<void> {
|
||||||
|
const connection = await connect(transactorUrl, dbName)
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('loading cvs document...')
|
||||||
|
|
||||||
|
const csvData = await readFile(csvFile, 'utf-8')
|
||||||
|
const records: any[] = await parseCSV(csvData)
|
||||||
|
const uniqKeys: string[] = []
|
||||||
|
const filledFields = records.map((it) => filled(it, uniqKeys))
|
||||||
|
// console.log(filledFields)
|
||||||
|
|
||||||
|
const client = new TxOperations(connection, 'core:account:lead-importer' as Ref<EmployeeAccount>)
|
||||||
|
|
||||||
|
await updateClasses(client, records, fieldMapping)
|
||||||
|
|
||||||
|
await createCustomers(client, filledFields)
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error(err)
|
||||||
|
} finally {
|
||||||
|
await connection.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createCustomers (client: TxOperations, filledFields: any[]): Promise<void> {
|
||||||
|
for (const record of filledFields) {
|
||||||
|
let orgId: Ref<Organization> = generateId()
|
||||||
|
const orgName = record[names.orgName]
|
||||||
|
if ((orgName?.toString() ?? '').trim().length === 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const org = await client.findOne(lead.mixin.Customer, { name: orgName })
|
||||||
|
console.log('processing', orgName)
|
||||||
|
if (org === undefined) {
|
||||||
|
await client.createDoc(
|
||||||
|
contact.class.Organization,
|
||||||
|
contact.space.Contacts,
|
||||||
|
{
|
||||||
|
name: orgName,
|
||||||
|
city: '',
|
||||||
|
members: 0
|
||||||
|
},
|
||||||
|
orgId as unknown as Ref<Organization>
|
||||||
|
)
|
||||||
|
await client.createMixin<Contact, CustomCustomer>(
|
||||||
|
orgId,
|
||||||
|
contact.class.Organization,
|
||||||
|
contact.space.Contacts,
|
||||||
|
lead.mixin.Customer,
|
||||||
|
{
|
||||||
|
description: record[names.description],
|
||||||
|
dialog_area: record[names.dialog_area],
|
||||||
|
sales_manager: record[names.sales_manager],
|
||||||
|
bot_account: record[names.bot_account],
|
||||||
|
contacts: record[names.contacts],
|
||||||
|
contact_person: record[names.contact_person]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
orgId = org._id as unknown as Ref<Organization>
|
||||||
|
const upd: MixinUpdate<Contact, CustomCustomer> = {}
|
||||||
|
const newValues = {
|
||||||
|
description: record[names.description],
|
||||||
|
dialog_area: record[names.dialog_area],
|
||||||
|
sales_manager: record[names.sales_manager],
|
||||||
|
bot_account: record[names.bot_account],
|
||||||
|
contact_person: record[names.contact_person]
|
||||||
|
}
|
||||||
|
for (const [k, v] of Object.entries(newValues)) {
|
||||||
|
if ((org as any)[k] !== v && v !== undefined) {
|
||||||
|
;(upd as any)[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Object.keys(upd).length > 0) {
|
||||||
|
await client.updateMixin<Contact, CustomCustomer>(
|
||||||
|
orgId,
|
||||||
|
contact.class.Organization,
|
||||||
|
contact.space.Contacts,
|
||||||
|
lead.mixin.Customer,
|
||||||
|
upd
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const contacts: string = record[names.contacts]
|
||||||
|
|
||||||
|
let dta: string = contacts?.toString() ?? ''
|
||||||
|
if (dta.length > 0) {
|
||||||
|
dta = dta.replace('.ru ', '.ru\n')
|
||||||
|
dta = dta.replace('.com ', '.com\n')
|
||||||
|
dta = dta.replace('+', '\n+')
|
||||||
|
dta = dta.replace(',', '\n')
|
||||||
|
dta = dta.replace('|', '\n')
|
||||||
|
dta = dta.replace(' ', '\n')
|
||||||
|
dta = dta.replace('Email:', '\n')
|
||||||
|
dta = dta.replace('Email:', '\n')
|
||||||
|
const res = dta
|
||||||
|
.split('\n')
|
||||||
|
.map((it) => it.trim())
|
||||||
|
.filter((it) => it.length > 0)
|
||||||
|
// console.log('\t', res)
|
||||||
|
for (const r of res) {
|
||||||
|
const phone = parsePhoneNumber(r)
|
||||||
|
if (phone !== undefined) {
|
||||||
|
const iphone = phone.formatInternational()
|
||||||
|
const channels = await client.findAll(contact.class.Channel, { attachedTo: orgId })
|
||||||
|
const emailPr = channels.find((it) => it.value === iphone)
|
||||||
|
|
||||||
|
if (emailPr === undefined) {
|
||||||
|
await client.addCollection(
|
||||||
|
contact.class.Channel,
|
||||||
|
contact.space.Contacts,
|
||||||
|
orgId,
|
||||||
|
contact.class.Organization,
|
||||||
|
'channels',
|
||||||
|
{
|
||||||
|
value: iphone,
|
||||||
|
provider: contact.channelProvider.Phone
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedddr = addrs.parseOneAddress(r)
|
||||||
|
if (parsedddr != null) {
|
||||||
|
const email = r.trim()
|
||||||
|
if (email !== undefined) {
|
||||||
|
const channels = await client.findAll(contact.class.Channel, { attachedTo: orgId })
|
||||||
|
const emailPr = channels.find((it) => it.value === email)
|
||||||
|
if (emailPr === undefined) {
|
||||||
|
await client.addCollection(
|
||||||
|
contact.class.Channel,
|
||||||
|
contact.space.Contacts,
|
||||||
|
orgId,
|
||||||
|
contact.class.Organization,
|
||||||
|
'channels',
|
||||||
|
{
|
||||||
|
value: email,
|
||||||
|
provider: contact.channelProvider.Email
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const site = record[names.corporate_site]
|
||||||
|
if (site !== undefined) {
|
||||||
|
const channels = await client.findAll(contact.class.Channel, { attachedTo: orgId })
|
||||||
|
const sitePr = channels.find((it) => it.value === site)
|
||||||
|
if (sitePr === undefined) {
|
||||||
|
await client.addCollection(
|
||||||
|
contact.class.Channel,
|
||||||
|
contact.space.Contacts,
|
||||||
|
orgId,
|
||||||
|
contact.class.Organization,
|
||||||
|
'channels',
|
||||||
|
{
|
||||||
|
value: site,
|
||||||
|
provider: contact.channelProvider.Homepage
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
dev/tool/src/leads/types.ts
Normal file
43
dev/tool/src/leads/types.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
//
|
||||||
|
// Copyright © 2022 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
import { Class, Doc, Ref } from '@anticrm/core'
|
||||||
|
import { Customer } from '@anticrm/lead'
|
||||||
|
import { IntlString } from '@anticrm/platform'
|
||||||
|
|
||||||
|
export interface CustomCustomer extends Customer {
|
||||||
|
annualTurnover?: string
|
||||||
|
currency?: string
|
||||||
|
comment?: string
|
||||||
|
source_ka?: string
|
||||||
|
employees_count?: number
|
||||||
|
activity_area?: string
|
||||||
|
company_type?: string
|
||||||
|
processing_status?: string
|
||||||
|
responsible?: string
|
||||||
|
|
||||||
|
dialog_area?: string
|
||||||
|
sales_manager?: string
|
||||||
|
bot_account?: string
|
||||||
|
contacts?: string
|
||||||
|
contact_person?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FieldType {
|
||||||
|
name: string
|
||||||
|
type?: Ref<Class<Doc>>
|
||||||
|
label?: IntlString
|
||||||
|
enumName?: string
|
||||||
|
fName?: string
|
||||||
|
}
|
28
dev/tool/src/leads/utils.ts
Normal file
28
dev/tool/src/leads/utils.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
//
|
||||||
|
// Copyright © 2022 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
export function filled (obj: any, uniqKeys: string[]): any {
|
||||||
|
const result: Record<string, any> = {}
|
||||||
|
for (const [k, v] of Object.entries(obj)) {
|
||||||
|
if (typeof v === 'string' && v.trim().length === 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (!uniqKeys.includes(k)) {
|
||||||
|
uniqKeys.push(k)
|
||||||
|
}
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user