mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-18 06:13:52 +00:00
parent
c726f10dd8
commit
a5d261bed0
@ -86,6 +86,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{:else if node.nodeName === 'SPAN'}
|
{:else if node.nodeName === 'SPAN'}
|
||||||
|
<span style={node.getAttribute('style')}>
|
||||||
|
<svelte:self nodes={node.childNodes} />
|
||||||
|
{#if node.getAttribute('data-objectclass') !== undefined && node.getAttribute('data-id') !== undefined}
|
||||||
<Component
|
<Component
|
||||||
is={view.component.ObjectPresenter}
|
is={view.component.ObjectPresenter}
|
||||||
props={{
|
props={{
|
||||||
@ -97,6 +100,8 @@
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
{:else if node.nodeName === 'TABLE'}
|
{:else if node.nodeName === 'TABLE'}
|
||||||
<table class={node.className}><svelte:self nodes={node.childNodes} /></table>
|
<table class={node.className}><svelte:self nodes={node.childNodes} /></table>
|
||||||
{:else if node.nodeName === 'TBODY'}
|
{:else if node.nodeName === 'TBODY'}
|
||||||
|
@ -15,14 +15,22 @@
|
|||||||
//
|
//
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import presentation, { Card, createQuery } from '@hcengineering/presentation'
|
import presentation, { Card, createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { Integration } from '@hcengineering/setting'
|
import { Integration } from '@hcengineering/setting'
|
||||||
import { createEventDispatcher, onMount } from 'svelte'
|
import { createEventDispatcher, onMount } from 'svelte'
|
||||||
import bitrix from '../plugin'
|
import bitrix from '../plugin'
|
||||||
|
|
||||||
import { BitrixClient, BitrixEntityMapping, BitrixProfile, StatusValue } from '@hcengineering/bitrix'
|
import {
|
||||||
|
BitrixClient,
|
||||||
|
BitrixEntityMapping,
|
||||||
|
BitrixFieldMapping,
|
||||||
|
BitrixProfile,
|
||||||
|
StatusValue
|
||||||
|
} from '@hcengineering/bitrix'
|
||||||
import { Button, eventToHTMLElement, IconAdd, showPopup } from '@hcengineering/ui'
|
import { Button, eventToHTMLElement, IconAdd, showPopup } from '@hcengineering/ui'
|
||||||
|
|
||||||
|
import { Data, Doc, Ref } from '@hcengineering/core'
|
||||||
|
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||||
import { bitrixQueue } from '../queue'
|
import { bitrixQueue } from '../queue'
|
||||||
import CreateMapping from './CreateMapping.svelte'
|
import CreateMapping from './CreateMapping.svelte'
|
||||||
import EntityMapping from './EntityMapping.svelte'
|
import EntityMapping from './EntityMapping.svelte'
|
||||||
@ -47,6 +55,9 @@
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let inputFile: HTMLInputElement
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
const mQuery = createQuery()
|
const mQuery = createQuery()
|
||||||
|
|
||||||
let mappings: BitrixEntityMapping[] = []
|
let mappings: BitrixEntityMapping[] = []
|
||||||
@ -57,6 +68,68 @@
|
|||||||
function addMapping (evt: MouseEvent): void {
|
function addMapping (evt: MouseEvent): void {
|
||||||
showPopup(CreateMapping, { integration, mappings, bitrixClient }, eventToHTMLElement(evt))
|
showPopup(CreateMapping, { integration, mappings, bitrixClient }, eventToHTMLElement(evt))
|
||||||
}
|
}
|
||||||
|
const signature = '@#253heyf@'
|
||||||
|
const downloadConfig = async () => {
|
||||||
|
const filename = 'bitrix-config_' + new Date().toLocaleDateString() + '.json'
|
||||||
|
const link = document.createElement('a')
|
||||||
|
const fields = await client.findAll(bitrix.class.FieldMapping, {})
|
||||||
|
link.style.display = 'none'
|
||||||
|
link.setAttribute('target', '_blank')
|
||||||
|
link.setAttribute(
|
||||||
|
'href',
|
||||||
|
'data:text/json;charset=utf-8,%EF%BB%BF' +
|
||||||
|
encodeURIComponent(JSON.stringify({ mappings, fields, signature }, undefined, 2))
|
||||||
|
)
|
||||||
|
link.setAttribute('download', filename)
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
|
}
|
||||||
|
const replaceConfig = async () => {
|
||||||
|
inputFile.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileSelected = async () => {
|
||||||
|
const file = inputFile.files?.[0]
|
||||||
|
const text = await file?.text()
|
||||||
|
if (text === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const jsonParsed = JSON.parse(text)
|
||||||
|
if (jsonParsed.signature !== signature) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const op = client.apply('bitrix')
|
||||||
|
// Remove all stuff
|
||||||
|
for (const d of await (
|
||||||
|
await client.findAll<Doc>(bitrix.class.EntityMapping, {})
|
||||||
|
).concat(...(await client.findAll<Doc>(bitrix.class.FieldMapping, {})))) {
|
||||||
|
await op.remove(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import new items.
|
||||||
|
const mappings = jsonParsed.mappings as BitrixEntityMapping[]
|
||||||
|
for (const m of mappings) {
|
||||||
|
const { _class, space, _id, ...dta } = m
|
||||||
|
await op.tx(op.txFactory.createTxCreateDoc(_class, space, dta, _id))
|
||||||
|
}
|
||||||
|
const fields = jsonParsed.fields as BitrixFieldMapping[]
|
||||||
|
for (const m of fields) {
|
||||||
|
const { _class, space, _id, attachedTo, attachedToClass, collection, modifiedBy, modifiedOn, ...dta } = m
|
||||||
|
const cr = op.txFactory.createTxCreateDoc<BitrixFieldMapping>(_class, space, dta as Data<BitrixFieldMapping>, _id)
|
||||||
|
const col = op.txFactory.createTxCollectionCUD<BitrixEntityMapping, BitrixFieldMapping>(
|
||||||
|
attachedToClass,
|
||||||
|
attachedTo as Ref<BitrixEntityMapping>,
|
||||||
|
space,
|
||||||
|
collection,
|
||||||
|
cr
|
||||||
|
)
|
||||||
|
await op.tx(col)
|
||||||
|
}
|
||||||
|
|
||||||
|
await op.commit()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
@ -78,15 +151,20 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
{#if profile}
|
{#if profile}
|
||||||
<div class="flex flex-reverse flex-grab">
|
|
||||||
<Button icon={IconAdd} label={presentation.string.Add} on:click={addMapping} />
|
|
||||||
</div>
|
|
||||||
<div class="flex-row">
|
<div class="flex-row">
|
||||||
{#each mappings as mapping}
|
{#each mappings as mapping}
|
||||||
<EntityMapping {mapping} {bitrixClient} {statusList} />
|
<EntityMapping {mapping} {bitrixClient} {statusList} />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<!-- <EditBox label={bitrix.string.BitrixTokenUrl} bind:value={url} /> -->
|
<svelte:fragment slot="pool">
|
||||||
<svelte:fragment slot="pool" />
|
<div class="flex-row-center flex-grow flex-between">
|
||||||
|
<Button icon={IconAdd} label={presentation.string.Add} on:click={addMapping} />
|
||||||
|
<div class="flex-row-center">
|
||||||
|
<Button label={getEmbeddedLabel('Download...')} on:click={downloadConfig} />
|
||||||
|
<Button label={getEmbeddedLabel('Replace...')} on:click={replaceConfig} />
|
||||||
|
<input bind:this={inputFile} type="file" name="file" id="file" style="display: none" on:change={fileSelected} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</svelte:fragment>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
BitrixClient,
|
BitrixClient,
|
||||||
BitrixEntityMapping,
|
BitrixEntityMapping,
|
||||||
BitrixFieldMapping,
|
BitrixFieldMapping,
|
||||||
|
defaultSyncPeriod,
|
||||||
Fields,
|
Fields,
|
||||||
performSynchronization,
|
performSynchronization,
|
||||||
StatusValue,
|
StatusValue,
|
||||||
@ -34,6 +35,7 @@
|
|||||||
let direction: 'ASC' | 'DSC' = 'ASC'
|
let direction: 'ASC' | 'DSC' = 'ASC'
|
||||||
let limit = 1
|
let limit = 1
|
||||||
let space: Ref<Space> | undefined
|
let space: Ref<Space> | undefined
|
||||||
|
let syncPeriod = defaultSyncPeriod
|
||||||
|
|
||||||
export let loading = false
|
export let loading = false
|
||||||
let state = ''
|
let state = ''
|
||||||
@ -67,7 +69,8 @@
|
|||||||
docsProcessed++
|
docsProcessed++
|
||||||
state = `processed: ${docsProcessed}/${total ?? 1}`
|
state = `processed: ${docsProcessed}/${total ?? 1}`
|
||||||
},
|
},
|
||||||
extraFilter: filterFields.length === 0 ? undefined : mappedFilter
|
extraFilter: filterFields.length === 0 ? undefined : mappedFilter,
|
||||||
|
syncPeriod
|
||||||
})
|
})
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
state = err.message
|
state = err.message
|
||||||
@ -141,6 +144,17 @@
|
|||||||
]}
|
]}
|
||||||
bind:selected={direction}
|
bind:selected={direction}
|
||||||
/>
|
/>
|
||||||
|
<NumberEditor
|
||||||
|
kind={'button'}
|
||||||
|
value={syncPeriod}
|
||||||
|
focus={false}
|
||||||
|
placeholder={getEmbeddedLabel('Period')}
|
||||||
|
onChange={(val) => {
|
||||||
|
if (val) {
|
||||||
|
syncPeriod = val
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<div class="fs-title">
|
<div class="fs-title">
|
||||||
<NumberEditor
|
<NumberEditor
|
||||||
kind={'button'}
|
kind={'button'}
|
||||||
|
@ -132,17 +132,24 @@ export async function syncDocument (
|
|||||||
for (const [cl, vals] of byClass.entries()) {
|
for (const [cl, vals] of byClass.entries()) {
|
||||||
if (applyOp.getHierarchy().isDerived(cl, core.class.AttachedDoc)) {
|
if (applyOp.getHierarchy().isDerived(cl, core.class.AttachedDoc)) {
|
||||||
const existingByClass = await client.findAll(cl, {
|
const existingByClass = await client.findAll(cl, {
|
||||||
attachedTo: resultDoc.document._id,
|
attachedTo: resultDoc.document._id
|
||||||
[bitrix.mixin.BitrixSyncDoc + '.bitrixId']: { $in: vals.map((it) => it.bitrixId) }
|
|
||||||
})
|
})
|
||||||
|
|
||||||
for (const valValue of vals) {
|
for (const valValue of vals) {
|
||||||
const existing = existingByClass.find(
|
const existingIdx = existingByClass.findIndex(
|
||||||
(it) => hierarchy.as<Doc, BitrixSyncDoc>(it, bitrix.mixin.BitrixSyncDoc).bitrixId === valValue.bitrixId
|
(it) => hierarchy.as<Doc, BitrixSyncDoc>(it, bitrix.mixin.BitrixSyncDoc).bitrixId === valValue.bitrixId
|
||||||
)
|
)
|
||||||
|
if (existingIdx >= 0) {
|
||||||
|
const existing = existingByClass.splice(existingIdx, 1).shift()
|
||||||
await updateAttachedDoc(existing, applyOp, valValue)
|
await updateAttachedDoc(existing, applyOp, valValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove previous merged documents, probable they are deleted in bitrix or wrongly migrated.
|
||||||
|
for (const doc of existingByClass) {
|
||||||
|
await client.remove(doc)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingBlobs = await client.findAll(attachment.class.Attachment, {
|
const existingBlobs = await client.findAll(attachment.class.Attachment, {
|
||||||
@ -355,8 +362,10 @@ export function processComment (comment: string): string {
|
|||||||
return comment
|
return comment
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1 day
|
/**
|
||||||
const syncPeriod = 1000 * 60 * 60 * 24
|
* @public
|
||||||
|
*/
|
||||||
|
export const defaultSyncPeriod = 1000 * 60 * 60 * 24
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -373,6 +382,7 @@ export interface SyncOptions {
|
|||||||
monitor: (total: number) => void
|
monitor: (total: number) => void
|
||||||
blobProvider?: (blobRef: { file: string, id: string }) => Promise<Blob | undefined>
|
blobProvider?: (blobRef: { file: string, id: string }) => Promise<Blob | undefined>
|
||||||
extraFilter?: Record<string, any>
|
extraFilter?: Record<string, any>
|
||||||
|
syncPeriod?: number
|
||||||
}
|
}
|
||||||
interface SyncOptionsExtra {
|
interface SyncOptionsExtra {
|
||||||
ownerTypeValues: BitrixOwnerType[]
|
ownerTypeValues: BitrixOwnerType[]
|
||||||
@ -471,7 +481,7 @@ async function doPerformSync (ops: SyncOptions & SyncOptionsExtra): Promise<Bitr
|
|||||||
)
|
)
|
||||||
if (existingDoc !== undefined) {
|
if (existingDoc !== undefined) {
|
||||||
const bd = ops.client.getHierarchy().as(existingDoc, bitrix.mixin.BitrixSyncDoc)
|
const bd = ops.client.getHierarchy().as(existingDoc, bitrix.mixin.BitrixSyncDoc)
|
||||||
if (bd.syncTime !== undefined && bd.syncTime + syncPeriod > syncTime) {
|
if (bd.syncTime !== undefined && bd.syncTime + (ops.syncPeriod ?? defaultSyncPeriod) > syncTime) {
|
||||||
// No need to sync, sime sync time is not yet arrived.
|
// No need to sync, sime sync time is not yet arrived.
|
||||||
toProcess.splice(0, 1)
|
toProcess.splice(0, 1)
|
||||||
added++
|
added++
|
||||||
@ -694,14 +704,14 @@ async function downloadComments (
|
|||||||
for (const comm of cr) {
|
for (const comm of cr) {
|
||||||
const cummunications = comm.COMMUNICATIONS?.map((it) => it.ENTITY_SETTINGS?.LEAD_TITLE ?? '')
|
const cummunications = comm.COMMUNICATIONS?.map((it) => it.ENTITY_SETTINGS?.LEAD_TITLE ?? '')
|
||||||
let message = `<p>
|
let message = `<p>
|
||||||
e-mail: ${cummunications?.join(',') ?? ''}<br/>\n
|
<span style="color: var(--primary-color-skyblue);">e-mail: ${cummunications?.join(',') ?? ''}</span><br/>\n
|
||||||
Subject: ${comm.SUBJECT}<br/>\n`
|
<span style="color: var(--primary-color-skyblue);">Subject: ${comm.SUBJECT}</span><br/>\n`
|
||||||
|
|
||||||
for (const [k, v] of Object.entries(comm.SETTINGS?.EMAIL_META ?? {}).concat(
|
for (const [k, v] of Object.entries(comm.SETTINGS?.EMAIL_META ?? {}).concat(
|
||||||
Object.entries(comm.SETTINGS?.MESSAGE_HEADERS ?? {})
|
Object.entries(comm.SETTINGS?.MESSAGE_HEADERS ?? {})
|
||||||
)) {
|
)) {
|
||||||
if (v.trim().length > 0) {
|
if (v.trim().length > 0) {
|
||||||
message += `<div>${k}: ${v}</div><br/>\n`
|
message += `<span style="color: var(--primary-color-skyblue);">${k}: ${v}</span><br/>\n`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
message += '</p>' + comm.DESCRIPTION
|
message += '</p>' + comm.DESCRIPTION
|
||||||
|
@ -106,6 +106,7 @@ export function serveAccount (methods: Record<string, AccountMethod>, productId
|
|||||||
|
|
||||||
const close = (): void => {
|
const close = (): void => {
|
||||||
server.close()
|
server.close()
|
||||||
|
process.exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
process.on('uncaughtException', (e) => {
|
process.on('uncaughtException', (e) => {
|
||||||
|
@ -516,5 +516,18 @@ export class FullTextIndexPipeline implements FullTextPipeline {
|
|||||||
.map((it) => it._id)
|
.map((it) => it._id)
|
||||||
await this.storage.clean(DOMAIN_DOC_INDEX_STATE, docIds)
|
await this.storage.clean(DOMAIN_DOC_INDEX_STATE, docIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean for non existing clases
|
||||||
|
|
||||||
|
const unknownClasses = (
|
||||||
|
await this.storage.findAll(
|
||||||
|
core.class.DocIndexState,
|
||||||
|
{ objectClass: { $nin: allClasses } },
|
||||||
|
{ projection: { _id: 1 } }
|
||||||
|
)
|
||||||
|
).map((it) => it._id)
|
||||||
|
if (unknownClasses.length > 0) {
|
||||||
|
await this.storage.clean(DOMAIN_DOC_INDEX_STATE, unknownClasses)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user