mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-30 13:35:04 +02:00
TSK-476: Bitrix import fixes (#2548)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user