TSK-476: Bitrix import fixes (#2548)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev
2023-01-27 18:14:40 +06:00
committed by GitHub
parent dd68380e7e
commit 474cb9c887
27 changed files with 948 additions and 562 deletions
@@ -1,35 +1,19 @@
<script lang="ts">
import bitrix, {
import {
BitrixClient,
BitrixEntityMapping,
BitrixEntityType,
BitrixFieldMapping,
BitrixSyncDoc
performSynchronization,
toClassRef
} from '@hcengineering/bitrix'
import chunter, { Comment } from '@hcengineering/chunter'
import contact, { combineName, EmployeeAccount } from '@hcengineering/contact'
import core, {
AccountRole,
ApplyOperations,
AttachedDoc,
Class,
Data,
Doc,
DocumentUpdate,
generateId,
Mixin,
Ref,
Space,
WithLookup
} from '@hcengineering/core'
import { getEmbeddedLabel } from '@hcengineering/platform'
import contact from '@hcengineering/contact'
import core, { Class, Doc, Ref, Space, WithLookup } from '@hcengineering/core'
import login from '@hcengineering/login'
import { getEmbeddedLabel, getMetadata } from '@hcengineering/platform'
import { getClient, SpaceSelect } from '@hcengineering/presentation'
import { TagElement } from '@hcengineering/tags'
import { Button, Expandable, Icon, Label } from '@hcengineering/ui'
import DropdownLabels from '@hcengineering/ui/src/components/DropdownLabels.svelte'
import { NumberEditor } from '@hcengineering/view-resources'
import { deepEqual } from 'fast-equals'
import { BitrixClient } from '../client'
import { convert, ConvertResult, toClassRef } from '../utils'
import FieldMappingPresenter from './FieldMappingPresenter.svelte'
export let mapping: WithLookup<BitrixEntityMapping>
@@ -43,275 +27,38 @@
return p
}, {} as Record<Ref<Class<Doc>>, BitrixFieldMapping[]>)
let direction: 'ASC' | 'DSC' = 'DSC'
let limit = 200
let direction: 'ASC' | 'DSC' = 'ASC'
let limit = 1
let space: Ref<Space> | undefined
export let loading = false
let state = ''
async function updateDoc (client: ApplyOperations, doc: Doc, raw: Doc | Data<Doc>): Promise<void> {
// We need to update fields if they are different.
const documentUpdate: DocumentUpdate<Doc> = {}
for (const [k, v] of Object.entries(raw)) {
if (['_class', '_id', 'modifiedBy', 'modifiedOn', 'space'].includes(k)) {
continue
}
if (!deepEqual((doc as any)[k], v)) {
;(documentUpdate as any)[k] = v
}
}
if (Object.keys(documentUpdate).length > 0) {
await client.update(doc, documentUpdate)
}
}
let docsProcessed = 0
let total = 0
async function syncPlatform (documents: ConvertResult[]): Promise<void> {
const existingDocuments = await client.findAll(mapping.ofClass, {
[bitrix.mixin.BitrixSyncDoc + '.bitrixId']: { $in: documents.map((it) => it.document.bitrixId) }
})
const hierarchy = client.getHierarchy()
for (const d of documents) {
const existing = existingDocuments.find(
(it) => hierarchy.as(it, bitrix.mixin.BitrixSyncDoc).bitrixId === d.document.bitrixId
)
const applyOp = client.apply('bitrix')
if (existing !== undefined) {
// We need to update fields if they are different.
await updateDoc(applyOp, existing, d.document)
// Check and update mixins
for (const [m, mv] of Object.entries(d.mixins)) {
const mRef = m as Ref<Mixin<Doc>>
if (hierarchy.hasMixin(existing, mRef)) {
await applyOp.createMixin(
d.document._id,
d.document._class,
d.document.space,
m as Ref<Mixin<Doc>>,
mv,
d.document.modifiedOn,
d.document.modifiedBy
)
} else {
const existingM = hierarchy.as(existing, mRef)
await updateDoc(applyOp, existingM, mv)
}
}
} else {
await applyOp.createDoc(
d.document._class,
d.document.space,
d.document,
d.document._id,
d.document.modifiedOn,
d.document.modifiedBy
)
await applyOp.createMixin<Doc, BitrixSyncDoc>(
d.document._id,
d.document._class,
d.document.space,
bitrix.mixin.BitrixSyncDoc,
{
type: d.document.type,
bitrixId: d.document.bitrixId
},
d.document.modifiedOn,
d.document.modifiedBy
)
for (const [m, mv] of Object.entries(d.mixins)) {
await applyOp.createMixin(
d.document._id,
d.document._class,
d.document.space,
m as Ref<Mixin<Doc>>,
mv,
d.document.modifiedOn,
d.document.modifiedBy
)
}
for (const ed of d.extraDocs) {
if (applyOp.getHierarchy().isDerived(ed._class, core.class.AttachedDoc)) {
const adoc = ed as AttachedDoc
await applyOp.addCollection(
adoc._class,
adoc.space,
adoc.attachedTo,
adoc.attachedToClass,
adoc.collection,
adoc,
adoc._id,
d.document.modifiedOn,
d.document.modifiedBy
)
} else {
await applyOp.createDoc(ed._class, ed.space, ed, ed._id, d.document.modifiedOn, d.document.modifiedBy)
}
}
if (d.comments !== undefined) {
const comments = await d.comments
if (comments !== undefined && comments.length > 0) {
const existingComments = await client.findAll(chunter.class.Comment, {
attachedTo: d.document._id,
[bitrix.mixin.BitrixSyncDoc + '.bitrixId']: { $in: comments.map((it) => it.bitrixId) }
})
for (const comment of comments) {
const existing = existingComments.find(
(it) => hierarchy.as<Doc, BitrixSyncDoc>(it, bitrix.mixin.BitrixSyncDoc).bitrixId === comment.bitrixId
)
if (existing !== undefined) {
// We need to update fields if they are different.
await updateDoc(applyOp, existing, comment)
} else {
await applyOp.addCollection(
comment._class,
comment.space,
comment.attachedTo,
comment.attachedToClass,
comment.collection,
comment,
comment._id,
comment.modifiedOn,
comment.modifiedBy
)
await applyOp.createMixin<Doc, BitrixSyncDoc>(
comment._id,
comment._class,
comment.space,
bitrix.mixin.BitrixSyncDoc,
{
type: d.document.type,
bitrixId: d.document.bitrixId
},
comment.modifiedOn,
comment.modifiedBy
)
}
}
}
}
}
await applyOp.commit()
docsProcessed++
state = `processed: ${docsProcessed}/${total}`
}
}
async function doSync (): Promise<void> {
loading = true
const commentFields = await bitrixClient.call(BitrixEntityType.Comment + '.fields', {})
const commentFieldKeys = Object.keys(commentFields.result)
const allEmployee = await client.findAll(contact.class.EmployeeAccount, {})
const userList = new Map<string, Ref<EmployeeAccount>>()
// Fill all users and create new ones, if required.
let totalUsers = 1
let next = 0
while (userList.size < totalUsers) {
const users = await bitrixClient.call('user.search', { start: next })
next = users.next
totalUsers = users.total
for (const u of users.result) {
let accountId = allEmployee.find((it) => it.email === u.EMAIL)?._id
if (accountId === undefined) {
const employeeId = await client.createDoc(contact.class.Employee, contact.space.Contacts, {
name: combineName(u.NAME, u.LAST_NAME),
avatar: u.PERSONAL_PHOTO,
active: u.ACTIVE,
city: u.PERSONAL_CITY
})
accountId = await client.createDoc(contact.class.EmployeeAccount, core.space.Model, {
email: u.EMAIL,
name: combineName(u.NAME, u.LAST_NAME),
employee: employeeId,
role: AccountRole.User
})
}
userList.set(u.ID, accountId)
}
}
const uploadUrl = (window.location.origin + getMetadata(login.metadata.UploadUrl)) as string
const token = (getMetadata(login.metadata.LoginToken) as string) ?? ''
try {
if (space === undefined || mapping.$lookup?.fields === undefined) {
return
}
let processed = 0
const tagElements: Map<Ref<Class<Doc>>, TagElement[]> = new Map()
let added = 0
while (added <= limit) {
const sel = ['*', 'UF_*']
if (mapping.type === BitrixEntityType.Lead) {
sel.push('EMAIL')
sel.push('IM')
await performSynchronization({
bitrixClient,
client,
direction,
limit,
space,
mapping,
loginInfo: {
token,
email: '',
endpoint: ''
},
frontUrl: uploadUrl,
monitor: (total: number) => {
docsProcessed++
state = `processed: ${docsProcessed}/${total ?? 1}`
}
const result = await bitrixClient.call(mapping.type + '.list', {
select: sel,
order: { ID: direction },
start: processed
})
const extraDocs: Doc[] = []
const convertResults: ConvertResult[] = []
const fields = mapping.$lookup?.fields as BitrixFieldMapping[]
for (const r of result.result) {
// Convert documents.
const res = await convert(client, mapping, space, fields, r, extraDocs, tagElements, userList)
if (mapping.comments) {
res.comments = bitrixClient
.call(BitrixEntityType.Comment + '.list', {
filter: {
ENTITY_ID: res.document.bitrixId,
ENTITY_TYPE: mapping.type.replace('crm.', '')
},
select: commentFieldKeys,
order: { ID: direction }
})
.then((comments) => {
return comments.result.map(
(it: any) =>
({
_id: generateId(),
_class: chunter.class.Comment,
message: it.COMMENT,
bitrixId: it.ID,
type: it.ENTITY_TYPE,
attachedTo: res.document._id,
attachedToClass: res.document._class,
collection: 'comments',
space: res.document.space,
modifiedBy: userList.get(it.AUTHOR_ID) ?? core.account.System,
modifiedOn: new Date(userList.get(it.CREATED) ?? new Date().toString()).getTime()
} as Comment)
)
})
}
convertResults.push(res)
extraDocs.push(...res.extraDocs)
added++
if (added > limit) {
break
}
}
total = result.total
await syncPlatform(convertResults)
processed = result.next
}
})
} catch (err: any) {
state = err.message
console.error(err)