mirror of
https://github.com/hcengineering/platform.git
synced 2025-01-04 09:37:58 +03:00
parent
9a96035b00
commit
4a5532af10
@ -110,7 +110,8 @@
|
|||||||
{:else if node.nodeName === 'S'}
|
{:else if node.nodeName === 'S'}
|
||||||
<s><svelte:self nodes={node.childNodes} /></s>
|
<s><svelte:self nodes={node.childNodes} /></s>
|
||||||
{:else}
|
{:else}
|
||||||
Unknown {node.nodeName}
|
unknown: {node.nodeName}
|
||||||
|
<svelte:self nodes={node.childNodes} />
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
import { bitrixQueue } from '../queue'
|
import { bitrixQueue } from '../queue'
|
||||||
import CreateMapping from './CreateMapping.svelte'
|
import CreateMapping from './CreateMapping.svelte'
|
||||||
import EntiryMapping from './EntityMapping.svelte'
|
import EntityMapping from './EntityMapping.svelte'
|
||||||
|
|
||||||
export let integration: Integration
|
export let integration: Integration
|
||||||
|
|
||||||
@ -83,7 +83,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex-row">
|
<div class="flex-row">
|
||||||
{#each mappings as mapping}
|
{#each mappings as mapping}
|
||||||
<EntiryMapping {mapping} {bitrixClient} {statusList} />
|
<EntityMapping {mapping} {bitrixClient} {statusList} />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { BitrixClient, BitrixEntityMapping, Fields, FieldValue } from '@hcengineering/bitrix'
|
import { BitrixClient, BitrixEntityMapping, Fields, FieldValue } from '@hcengineering/bitrix'
|
||||||
import core, { Enum, Ref } from '@hcengineering/core'
|
import core, { DateRangeMode, Enum, Ref } from '@hcengineering/core'
|
||||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import setting from '@hcengineering/setting-resources/src/plugin'
|
import setting from '@hcengineering/setting-resources/src/plugin'
|
||||||
@ -125,7 +125,10 @@
|
|||||||
<div class="ml-2">
|
<div class="ml-2">
|
||||||
{#if value !== null && value !== undefined && value !== ''}
|
{#if value !== null && value !== undefined && value !== ''}
|
||||||
{#if (field.type === 'datetime' || field.type === 'date') && value != null && value !== ''}
|
{#if (field.type === 'datetime' || field.type === 'date') && value != null && value !== ''}
|
||||||
<DatePresenter value={new Date(value).getTime()} withTime={field.type === 'datetime'} />
|
<DatePresenter
|
||||||
|
value={new Date(value).getTime()}
|
||||||
|
mode={field.type === 'datetime' ? DateRangeMode.DATETIME : DateRangeMode.DATE}
|
||||||
|
/>
|
||||||
{:else if field.type === 'enumeration'}
|
{:else if field.type === 'enumeration'}
|
||||||
{field.items?.find((it) => it.ID === value)?.VALUE}
|
{field.items?.find((it) => it.ID === value)?.VALUE}
|
||||||
{:else}
|
{:else}
|
||||||
|
@ -3,17 +3,21 @@
|
|||||||
BitrixClient,
|
BitrixClient,
|
||||||
BitrixEntityMapping,
|
BitrixEntityMapping,
|
||||||
BitrixFieldMapping,
|
BitrixFieldMapping,
|
||||||
|
Fields,
|
||||||
performSynchronization,
|
performSynchronization,
|
||||||
|
StatusValue,
|
||||||
toClassRef
|
toClassRef
|
||||||
} from '@hcengineering/bitrix'
|
} from '@hcengineering/bitrix'
|
||||||
import contact from '@hcengineering/contact'
|
import contact from '@hcengineering/contact'
|
||||||
import core, { Class, Doc, Ref, Space, WithLookup } from '@hcengineering/core'
|
import core, { Class, Doc, generateId, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||||
import login from '@hcengineering/login'
|
import login from '@hcengineering/login'
|
||||||
import { getEmbeddedLabel, getMetadata } from '@hcengineering/platform'
|
import { getEmbeddedLabel, getMetadata } from '@hcengineering/platform'
|
||||||
import { getClient, SpaceSelect } from '@hcengineering/presentation'
|
import { getClient, SpaceSelect } from '@hcengineering/presentation'
|
||||||
import { Button, Expandable, Icon, Label } from '@hcengineering/ui'
|
import { Button, Expandable, Icon, IconAdd, IconClose, Label } from '@hcengineering/ui'
|
||||||
import DropdownLabels from '@hcengineering/ui/src/components/DropdownLabels.svelte'
|
import DropdownLabels from '@hcengineering/ui/src/components/DropdownLabels.svelte'
|
||||||
|
import EditBox from '@hcengineering/ui/src/components/EditBox.svelte'
|
||||||
import { NumberEditor } from '@hcengineering/view-resources'
|
import { NumberEditor } from '@hcengineering/view-resources'
|
||||||
|
import bitrix from '../plugin'
|
||||||
import FieldMappingPresenter from './FieldMappingPresenter.svelte'
|
import FieldMappingPresenter from './FieldMappingPresenter.svelte'
|
||||||
|
|
||||||
export let mapping: WithLookup<BitrixEntityMapping>
|
export let mapping: WithLookup<BitrixEntityMapping>
|
||||||
@ -40,6 +44,11 @@
|
|||||||
loading = true
|
loading = true
|
||||||
const uploadUrl = (window.location.origin + getMetadata(login.metadata.UploadUrl)) as string
|
const uploadUrl = (window.location.origin + getMetadata(login.metadata.UploadUrl)) as string
|
||||||
const token = (getMetadata(login.metadata.LoginToken) as string) ?? ''
|
const token = (getMetadata(login.metadata.LoginToken) as string) ?? ''
|
||||||
|
|
||||||
|
const mappedFilter: Record<string, any> = {}
|
||||||
|
for (const f of filterFields) {
|
||||||
|
mappedFilter[f.field] = f.value
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await performSynchronization({
|
await performSynchronization({
|
||||||
bitrixClient,
|
bitrixClient,
|
||||||
@ -57,7 +66,8 @@
|
|||||||
monitor: (total: number) => {
|
monitor: (total: number) => {
|
||||||
docsProcessed++
|
docsProcessed++
|
||||||
state = `processed: ${docsProcessed}/${total ?? 1}`
|
state = `processed: ${docsProcessed}/${total ?? 1}`
|
||||||
}
|
},
|
||||||
|
extraFilter: filterFields.length === 0 ? undefined : mappedFilter
|
||||||
})
|
})
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
state = err.message
|
state = err.message
|
||||||
@ -66,6 +76,49 @@
|
|||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const fieldsKey = bitrix.class.EntityMapping + '.fields.' + mapping._id
|
||||||
|
let filterFields: { _id: string; field: string; value: string }[] = []
|
||||||
|
|
||||||
|
const content = JSON.parse(localStorage.getItem(fieldsKey) ?? '[]')
|
||||||
|
|
||||||
|
filterFields = content.filterFields ?? []
|
||||||
|
limit = content.limit ?? 1
|
||||||
|
direction = content.direction ?? 'ASC'
|
||||||
|
|
||||||
|
$: localStorage.setItem(fieldsKey, JSON.stringify({ limit, filterFields, direction }))
|
||||||
|
|
||||||
|
function addFilter (): void {
|
||||||
|
filterFields = [...filterFields, { _id: generateId(), field: '', value: ' ' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
let fields: Fields = {}
|
||||||
|
bitrixClient.call(mapping.type + '.fields', {}).then((res) => {
|
||||||
|
fields = res.result
|
||||||
|
})
|
||||||
|
|
||||||
|
let statusList: StatusValue[] = []
|
||||||
|
|
||||||
|
bitrixClient.call('crm.status.list', {}).then((res) => {
|
||||||
|
statusList = res.result
|
||||||
|
})
|
||||||
|
|
||||||
|
$: items = Object.entries(fields).map((it) => ({
|
||||||
|
id: it[0],
|
||||||
|
label: `${it[1].formLabel ?? it[1].title}${it[0].startsWith('UF_') ? ' *' : ''} - ${it[0]}`
|
||||||
|
}))
|
||||||
|
|
||||||
|
function updateFields (fields: Fields, statusList: StatusValue[]): void {
|
||||||
|
// Update fields with status valies if missing.
|
||||||
|
for (const f of Object.values(fields)) {
|
||||||
|
if (f.type === 'crm_status') {
|
||||||
|
f.items = statusList
|
||||||
|
.filter((it) => it.ENTITY_ID === f.statusType)
|
||||||
|
.map((it) => ({ ID: `${it.STATUS_ID}`, VALUE: it.NAME }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: updateFields(fields, statusList)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Expandable label={getEmbeddedLabel(mapping.type)}>
|
<Expandable label={getEmbeddedLabel(mapping.type)}>
|
||||||
@ -102,6 +155,8 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons-divider" />
|
<div class="buttons-divider" />
|
||||||
|
<Button icon={IconAdd} on:click={addFilter} />
|
||||||
|
<div class="buttons-divider" />
|
||||||
<div class="flex-row-center">
|
<div class="flex-row-center">
|
||||||
<div class="p-1">
|
<div class="p-1">
|
||||||
{state}
|
{state}
|
||||||
@ -131,3 +186,29 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</Expandable>
|
</Expandable>
|
||||||
|
|
||||||
|
<div class="flex-row-center">
|
||||||
|
{#each filterFields as field, pos}
|
||||||
|
{@const fValue = fields[field.field]}
|
||||||
|
<div class="item flex-row-center">
|
||||||
|
<DropdownLabels minW0={false} label={bitrix.string.FieldMapping} {items} bind:selected={field.field} />
|
||||||
|
{#if fValue?.type === 'crm_status' || fValue?.type === 'enumeration'}
|
||||||
|
<DropdownLabels
|
||||||
|
minW0={false}
|
||||||
|
label={bitrix.string.FieldMapping}
|
||||||
|
items={fValue.items?.map((it) => ({ id: it.ID, label: it.VALUE })) ?? []}
|
||||||
|
bind:selected={field.value}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<EditBox bind:value={field.value} />
|
||||||
|
{/if}
|
||||||
|
<Button
|
||||||
|
icon={IconClose}
|
||||||
|
on:click={() => {
|
||||||
|
filterFields.splice(pos, 1)
|
||||||
|
filterFields = filterFields
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hcengineering/bitrix",
|
"name": "@hcengineering/bitrix",
|
||||||
"version": "0.6.16",
|
"version": "0.6.19",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"author": "Anticrm Platform Contributors",
|
"author": "Anticrm Platform Contributors",
|
||||||
"license": "EPL-2.0",
|
"license": "EPL-2.0",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||||
import chunter, { Comment } from '@hcengineering/chunter'
|
import chunter, { Comment } from '@hcengineering/chunter'
|
||||||
import contact, { combineName, EmployeeAccount } from '@hcengineering/contact'
|
import contact, { combineName, Contact, EmployeeAccount } from '@hcengineering/contact'
|
||||||
import core, {
|
import core, {
|
||||||
AccountRole,
|
AccountRole,
|
||||||
ApplyOperations,
|
ApplyOperations,
|
||||||
@ -30,6 +30,8 @@ import {
|
|||||||
BitrixEntityMapping,
|
BitrixEntityMapping,
|
||||||
BitrixEntityType,
|
BitrixEntityType,
|
||||||
BitrixFieldMapping,
|
BitrixFieldMapping,
|
||||||
|
BitrixFiles,
|
||||||
|
BitrixOwnerType,
|
||||||
BitrixSyncDoc,
|
BitrixSyncDoc,
|
||||||
LoginInfo
|
LoginInfo
|
||||||
} from './types'
|
} from './types'
|
||||||
@ -147,7 +149,7 @@ export async function syncDocument (
|
|||||||
attachedTo: resultDoc.document._id,
|
attachedTo: resultDoc.document._id,
|
||||||
[bitrix.mixin.BitrixSyncDoc + '.bitrixId']: { $in: resultDoc.blobs.map((it) => it[0].bitrixId) }
|
[bitrix.mixin.BitrixSyncDoc + '.bitrixId']: { $in: resultDoc.blobs.map((it) => it[0].bitrixId) }
|
||||||
})
|
})
|
||||||
for (const [ed, op] of resultDoc.blobs) {
|
for (const [ed, op, upd] of resultDoc.blobs) {
|
||||||
const existing = existingBlobs.find(
|
const existing = existingBlobs.find(
|
||||||
(it) => hierarchy.as<Doc, BitrixSyncDoc>(it, bitrix.mixin.BitrixSyncDoc).bitrixId === ed.bitrixId
|
(it) => hierarchy.as<Doc, BitrixSyncDoc>(it, bitrix.mixin.BitrixSyncDoc).bitrixId === ed.bitrixId
|
||||||
)
|
)
|
||||||
@ -171,13 +173,13 @@ export async function syncDocument (
|
|||||||
})
|
})
|
||||||
if (resp.status === 200) {
|
if (resp.status === 200) {
|
||||||
const uuid = await resp.text()
|
const uuid = await resp.text()
|
||||||
|
upd(edData, ed)
|
||||||
await applyOp.addCollection(
|
await applyOp.addCollection(
|
||||||
attachment.class.Attachment,
|
ed._class,
|
||||||
resultDoc.document.space,
|
ed.space,
|
||||||
resultDoc.document._id,
|
ed.attachedTo,
|
||||||
resultDoc.document._class,
|
ed.attachedToClass,
|
||||||
'attachments',
|
ed.collection,
|
||||||
{
|
{
|
||||||
file: uuid,
|
file: uuid,
|
||||||
lastModified: edData.lastModified,
|
lastModified: edData.lastModified,
|
||||||
@ -186,8 +188,8 @@ export async function syncDocument (
|
|||||||
type: edData.type
|
type: edData.type
|
||||||
},
|
},
|
||||||
attachmentId,
|
attachmentId,
|
||||||
resultDoc.document.modifiedOn,
|
ed.modifiedOn,
|
||||||
resultDoc.document.modifiedBy
|
ed.modifiedBy
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@ -355,10 +357,11 @@ export function processComment (comment: string): string {
|
|||||||
|
|
||||||
// 1 day
|
// 1 day
|
||||||
const syncPeriod = 1000 * 60 * 60 * 24
|
const syncPeriod = 1000 * 60 * 60 * 24
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export async function performSynchronization (ops: {
|
export interface SyncOptions {
|
||||||
client: TxOperations
|
client: TxOperations
|
||||||
bitrixClient: BitrixClient
|
bitrixClient: BitrixClient
|
||||||
space: Ref<Space> | undefined
|
space: Ref<Space> | undefined
|
||||||
@ -368,41 +371,83 @@ export async function performSynchronization (ops: {
|
|||||||
frontUrl: string
|
frontUrl: string
|
||||||
loginInfo: LoginInfo
|
loginInfo: LoginInfo
|
||||||
monitor: (total: number) => void
|
monitor: (total: number) => void
|
||||||
blobProvider?: (blobRef: any) => Promise<Blob | undefined>
|
blobProvider?: (blobRef: { file: string, id: string }) => Promise<Blob | undefined>
|
||||||
}): Promise<void> {
|
extraFilter?: Record<string, any>
|
||||||
|
}
|
||||||
|
interface SyncOptionsExtra {
|
||||||
|
ownerTypeValues: BitrixOwnerType[]
|
||||||
|
commentFieldKeys: string[]
|
||||||
|
allMappings: FindResult<BitrixEntityMapping>
|
||||||
|
allEmployee: FindResult<EmployeeAccount>
|
||||||
|
userList: Map<string, Ref<EmployeeAccount>>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export async function performSynchronization (ops: SyncOptions): Promise<BitrixSyncDoc[]> {
|
||||||
const commentFields = await ops.bitrixClient.call(BitrixEntityType.Comment + '.fields', {})
|
const commentFields = await ops.bitrixClient.call(BitrixEntityType.Comment + '.fields', {})
|
||||||
|
|
||||||
|
const ownerTypes = await ops.bitrixClient.call('crm.enum.ownertype', {})
|
||||||
|
|
||||||
|
const ownerTypeValues = ownerTypes.result as BitrixOwnerType[]
|
||||||
|
|
||||||
const commentFieldKeys = Object.keys(commentFields.result)
|
const commentFieldKeys = Object.keys(commentFields.result)
|
||||||
|
|
||||||
const allEmployee = await ops.client.findAll(contact.class.EmployeeAccount, {})
|
const allEmployee = await ops.client.findAll(contact.class.EmployeeAccount, {})
|
||||||
|
|
||||||
|
const allMappings = await ops.client.findAll<BitrixEntityMapping>(
|
||||||
|
bitrix.class.EntityMapping,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
lookup: {
|
||||||
|
_id: {
|
||||||
|
fields: bitrix.class.FieldMapping
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const userList = new Map<string, Ref<EmployeeAccount>>()
|
const userList = new Map<string, Ref<EmployeeAccount>>()
|
||||||
|
|
||||||
// Fill all users and create new ones, if required.
|
// Fill all users and create new ones, if required.
|
||||||
await synchronizeUsers(userList, ops, allEmployee)
|
await synchronizeUsers(userList, ops, allEmployee)
|
||||||
|
|
||||||
|
return await doPerformSync({
|
||||||
|
...ops,
|
||||||
|
ownerTypeValues,
|
||||||
|
commentFieldKeys,
|
||||||
|
allMappings,
|
||||||
|
allEmployee,
|
||||||
|
userList
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doPerformSync (ops: SyncOptions & SyncOptionsExtra): Promise<BitrixSyncDoc[]> {
|
||||||
|
const resultDocs: BitrixSyncDoc[] = []
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (ops.space === undefined || ops.mapping.$lookup?.fields === undefined) {
|
if (ops.space === undefined || ops.mapping.$lookup?.fields === undefined) {
|
||||||
return
|
return []
|
||||||
}
|
}
|
||||||
let processed = 0
|
let processed = 0
|
||||||
|
|
||||||
let added = 0
|
let added = 0
|
||||||
|
|
||||||
const sel = ['*', 'UF_*']
|
const sel = ['*', 'UF_*', 'EMAIL', 'IM']
|
||||||
if (ops.mapping.type === BitrixEntityType.Lead) {
|
|
||||||
sel.push('EMAIL')
|
|
||||||
sel.push('IM')
|
|
||||||
}
|
|
||||||
|
|
||||||
const allTagElements = await ops.client.findAll<TagElement>(tags.class.TagElement, {})
|
const allTagElements = await ops.client.findAll<TagElement>(tags.class.TagElement, {})
|
||||||
|
|
||||||
while (added < ops.limit) {
|
while (added < ops.limit) {
|
||||||
const result = await ops.bitrixClient.call(ops.mapping.type + '.list', {
|
const q: Record<string, any> = {
|
||||||
select: sel,
|
select: sel,
|
||||||
order: { ID: ops.direction },
|
order: { ID: ops.direction },
|
||||||
start: processed
|
start: processed
|
||||||
})
|
}
|
||||||
|
if (ops.extraFilter !== undefined) {
|
||||||
|
q.filter = ops.extraFilter
|
||||||
|
}
|
||||||
|
const result = await ops.bitrixClient.call(ops.mapping.type + '.list', q)
|
||||||
|
|
||||||
const fields = ops.mapping.$lookup?.fields as BitrixFieldMapping[]
|
const fields = ops.mapping.$lookup?.fields as BitrixFieldMapping[]
|
||||||
|
|
||||||
@ -445,7 +490,7 @@ export async function performSynchronization (ops: {
|
|||||||
ops.space,
|
ops.space,
|
||||||
fields,
|
fields,
|
||||||
r,
|
r,
|
||||||
userList,
|
ops.userList,
|
||||||
existingDoc,
|
existingDoc,
|
||||||
defaultCategories,
|
defaultCategories,
|
||||||
allTagElements,
|
allTagElements,
|
||||||
@ -453,7 +498,7 @@ export async function performSynchronization (ops: {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (ops.mapping.comments) {
|
if (ops.mapping.comments) {
|
||||||
await downloadComments(res, ops, commentFieldKeys, userList)
|
await downloadComments(res, ops, ops.commentFieldKeys, ops.userList, ops.ownerTypeValues)
|
||||||
}
|
}
|
||||||
|
|
||||||
added++
|
added++
|
||||||
@ -461,12 +506,34 @@ export async function performSynchronization (ops: {
|
|||||||
await syncDocument(ops.client, existingDoc, res, ops.loginInfo, ops.frontUrl, () => {
|
await syncDocument(ops.client, existingDoc, res, ops.loginInfo, ops.frontUrl, () => {
|
||||||
ops.monitor?.(total)
|
ops.monitor?.(total)
|
||||||
})
|
})
|
||||||
|
if (existingDoc !== undefined) {
|
||||||
|
res.document._id = existingDoc._id as Ref<BitrixSyncDoc>
|
||||||
|
}
|
||||||
|
resultDocs.push(res.document)
|
||||||
for (const d of res.extraDocs) {
|
for (const d of res.extraDocs) {
|
||||||
// update tags if required
|
// update tags if required
|
||||||
if (d._class === tags.class.TagElement) {
|
if (d._class === tags.class.TagElement) {
|
||||||
allTagElements.push(d as TagElement)
|
allTagElements.push(d as TagElement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ops.mapping.type === BitrixEntityType.Company) {
|
||||||
|
// We need to perform contact mapping if they are defined.
|
||||||
|
const contactMapping = ops.allMappings.find((it) => it.type === BitrixEntityType.Contact)
|
||||||
|
if (contactMapping !== undefined) {
|
||||||
|
await performOrganizationContactSynchronization(
|
||||||
|
{
|
||||||
|
...ops,
|
||||||
|
mapping: contactMapping,
|
||||||
|
limit: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
res
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (added >= ops.limit) {
|
if (added >= ops.limit) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -481,11 +548,53 @@ export async function performSynchronization (ops: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
processed = result.next
|
processed = result.next
|
||||||
|
if (processed === undefined) {
|
||||||
|
// No more elements
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
}
|
}
|
||||||
|
return resultDocs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function performOrganizationContactSynchronization (
|
||||||
|
ops: SyncOptions & SyncOptionsExtra,
|
||||||
|
extra: {
|
||||||
|
res: ConvertResult
|
||||||
|
}
|
||||||
|
): Promise<void> {
|
||||||
|
const contacts = await doPerformSync({
|
||||||
|
...ops,
|
||||||
|
extraFilter: { COMPANY_ID: extra.res.document.bitrixId },
|
||||||
|
monitor: (total) => {
|
||||||
|
console.log('total', total)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const existingContacts = await ops.client.findAll(contact.class.Member, {
|
||||||
|
attachedTo: extra.res.document._id,
|
||||||
|
contact: { $in: contacts.map((it) => it._id as unknown as Ref<Contact>) }
|
||||||
|
})
|
||||||
|
for (const c of contacts) {
|
||||||
|
const ex = existingContacts.find((e) => e.contact === (c._id as unknown as Ref<Contact>))
|
||||||
|
if (ex === undefined) {
|
||||||
|
await ops.client.addCollection(
|
||||||
|
contact.class.Member,
|
||||||
|
extra.res.document.space,
|
||||||
|
extra.res.document._id,
|
||||||
|
extra.res.document._class,
|
||||||
|
'members',
|
||||||
|
{
|
||||||
|
contact: c._id as unknown as Ref<Contact>
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to create Member's for organization contacts.
|
||||||
|
}
|
||||||
|
|
||||||
async function downloadComments (
|
async function downloadComments (
|
||||||
res: ConvertResult,
|
res: ConvertResult,
|
||||||
ops: {
|
ops: {
|
||||||
@ -498,15 +607,21 @@ async function downloadComments (
|
|||||||
frontUrl: string
|
frontUrl: string
|
||||||
loginInfo: LoginInfo
|
loginInfo: LoginInfo
|
||||||
monitor: (total: number) => void
|
monitor: (total: number) => void
|
||||||
blobProvider?: ((blobRef: any) => Promise<Blob | undefined>) | undefined
|
blobProvider?: ((blobRef: { file: string, id: string }) => Promise<Blob | undefined>) | undefined
|
||||||
},
|
},
|
||||||
commentFieldKeys: string[],
|
commentFieldKeys: string[],
|
||||||
userList: Map<string, Ref<EmployeeAccount>>
|
userList: Map<string, Ref<EmployeeAccount>>,
|
||||||
|
ownerTypeValues: BitrixOwnerType[]
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const entityType = ops.mapping.type.replace('crm.', '')
|
||||||
|
const ownerType = ownerTypeValues.find((it) => it.SYMBOL_CODE.toLowerCase() === entityType)
|
||||||
|
if (ownerType === undefined) {
|
||||||
|
throw new Error(`No owner type found for ${entityType}`)
|
||||||
|
}
|
||||||
const commentsData = await ops.bitrixClient.call(BitrixEntityType.Comment + '.list', {
|
const commentsData = await ops.bitrixClient.call(BitrixEntityType.Comment + '.list', {
|
||||||
filter: {
|
filter: {
|
||||||
ENTITY_ID: res.document.bitrixId,
|
ENTITY_ID: res.document.bitrixId,
|
||||||
ENTITY_TYPE: ops.mapping.type.replace('crm.', '')
|
ENTITY_TYPE: entityType
|
||||||
},
|
},
|
||||||
select: commentFieldKeys,
|
select: commentFieldKeys,
|
||||||
order: { ID: ops.direction }
|
order: { ID: ops.direction }
|
||||||
@ -523,14 +638,53 @@ async function downloadComments (
|
|||||||
collection: 'comments',
|
collection: 'comments',
|
||||||
space: res.document.space,
|
space: res.document.space,
|
||||||
modifiedBy: userList.get(it.AUTHOR_ID) ?? core.account.System,
|
modifiedBy: userList.get(it.AUTHOR_ID) ?? core.account.System,
|
||||||
modifiedOn: new Date(it.CREATED ?? new Date().toString()).getTime()
|
modifiedOn: new Date(it.CREATED ?? new Date().toString()).getTime(),
|
||||||
|
attachments: 0
|
||||||
|
}
|
||||||
|
if (Object.keys(it.FILES ?? {}).length > 0) {
|
||||||
|
for (const [, v] of Object.entries(it.FILES as BitrixFiles)) {
|
||||||
|
c.message += `</br> Attachment: <a href='${v.urlDownload}'>${v.name} by ${v.authorName}</a>`
|
||||||
|
// Direct link, we could download using fetch.
|
||||||
|
c.attachments = (c.attachments ?? 0) + 1
|
||||||
|
res.blobs.push([
|
||||||
|
{
|
||||||
|
_id: generateId(),
|
||||||
|
_class: attachment.class.Attachment,
|
||||||
|
attachedTo: c._id,
|
||||||
|
attachedToClass: c._class,
|
||||||
|
bitrixId: `attach-${v.id}`,
|
||||||
|
collection: 'attachments',
|
||||||
|
file: '',
|
||||||
|
lastModified: Date.now(),
|
||||||
|
modifiedBy: userList.get(it.AUTHOR_ID) ?? core.account.System,
|
||||||
|
modifiedOn: new Date(it.CREATED ?? new Date().toString()).getTime(),
|
||||||
|
name: v.name,
|
||||||
|
size: v.size,
|
||||||
|
space: c.space,
|
||||||
|
type: 'file'
|
||||||
|
},
|
||||||
|
async (): Promise<File | undefined> => {
|
||||||
|
const blob = await ops.blobProvider?.({ file: v.urlDownload, id: `${v.id}` })
|
||||||
|
if (blob !== undefined) {
|
||||||
|
return new File([blob], v.name)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(file: File, attach: Attachment) => {
|
||||||
|
attach.attachedTo = c._id
|
||||||
|
attach.type = file.type
|
||||||
|
attach.size = file.size
|
||||||
|
attach.name = file.name
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
res.extraSync.push(c)
|
res.extraSync.push(c)
|
||||||
}
|
}
|
||||||
const communications = await ops.bitrixClient.call('crm.activity.list', {
|
const communications = await ops.bitrixClient.call('crm.activity.list', {
|
||||||
order: { ID: 'DESC' },
|
order: { ID: 'DESC' },
|
||||||
filter: {
|
filter: {
|
||||||
OWNER_ID: res.document.bitrixId
|
OWNER_ID: res.document.bitrixId,
|
||||||
|
OWNER_TYPE: ownerType.ID
|
||||||
},
|
},
|
||||||
select: ['*', 'COMMUNICATIONS']
|
select: ['*', 'COMMUNICATIONS']
|
||||||
})
|
})
|
||||||
@ -538,12 +692,23 @@ async function downloadComments (
|
|||||||
? (communications.result as BitrixActivity[])
|
? (communications.result as BitrixActivity[])
|
||||||
: [communications.result as BitrixActivity]
|
: [communications.result as BitrixActivity]
|
||||||
for (const comm of cr) {
|
for (const comm of cr) {
|
||||||
|
const cummunications = comm.COMMUNICATIONS?.map((it) => it.ENTITY_SETTINGS?.LEAD_TITLE ?? '')
|
||||||
|
let message = `<p>
|
||||||
|
e-mail: ${cummunications?.join(',') ?? ''}<br/>\n
|
||||||
|
Subject: ${comm.SUBJECT}<br/>\n`
|
||||||
|
|
||||||
|
for (const [k, v] of Object.entries(comm.SETTINGS?.EMAIL_META ?? {}).concat(
|
||||||
|
Object.entries(comm.SETTINGS?.MESSAGE_HEADERS ?? {})
|
||||||
|
)) {
|
||||||
|
if (v.trim().length > 0) {
|
||||||
|
message += `<div>${k}: ${v}</div><br/>\n`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message += '</p>' + comm.DESCRIPTION
|
||||||
const c: Comment & { bitrixId: string, type: string } = {
|
const c: Comment & { bitrixId: string, type: string } = {
|
||||||
_id: generateId(),
|
_id: generateId(),
|
||||||
_class: chunter.class.Comment,
|
_class: chunter.class.Comment,
|
||||||
message: `e-mail:<br/>
|
message,
|
||||||
Subject: ${comm.SUBJECT}
|
|
||||||
${comm.DESCRIPTION}`,
|
|
||||||
bitrixId: comm.ID,
|
bitrixId: comm.ID,
|
||||||
type: 'email',
|
type: 'email',
|
||||||
attachedTo: res.document._id,
|
attachedTo: res.document._id,
|
||||||
@ -553,6 +718,7 @@ async function downloadComments (
|
|||||||
modifiedBy: userList.get(comm.AUTHOR_ID) ?? core.account.System,
|
modifiedBy: userList.get(comm.AUTHOR_ID) ?? core.account.System,
|
||||||
modifiedOn: new Date(comm.CREATED ?? new Date().toString()).getTime()
|
modifiedOn: new Date(comm.CREATED ?? new Date().toString()).getTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
res.extraSync.push(c)
|
res.extraSync.push(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -569,7 +735,7 @@ async function synchronizeUsers (
|
|||||||
frontUrl: string
|
frontUrl: string
|
||||||
loginInfo: LoginInfo
|
loginInfo: LoginInfo
|
||||||
monitor: (total: number) => void
|
monitor: (total: number) => void
|
||||||
blobProvider?: ((blobRef: any) => Promise<Blob | undefined>) | undefined
|
blobProvider?: ((blobRef: { file: string, id: string }) => Promise<Blob | undefined>) | undefined
|
||||||
},
|
},
|
||||||
allEmployee: FindResult<EmployeeAccount>
|
allEmployee: FindResult<EmployeeAccount>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
@ -101,7 +101,17 @@ export enum BitrixEntityType {
|
|||||||
Binding = 'crm.timeline.bindings',
|
Binding = 'crm.timeline.bindings',
|
||||||
Lead = 'crm.lead',
|
Lead = 'crm.lead',
|
||||||
Activity = 'crm.activity',
|
Activity = 'crm.activity',
|
||||||
Company = 'crm.company'
|
Company = 'crm.company',
|
||||||
|
Contact = 'crm.contact'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface BitrixOwnerType {
|
||||||
|
ID: string
|
||||||
|
NAME: string
|
||||||
|
SYMBOL_CODE: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -109,9 +119,8 @@ export enum BitrixEntityType {
|
|||||||
*/
|
*/
|
||||||
export const mappingTypes = [
|
export const mappingTypes = [
|
||||||
{ label: 'Leads', id: BitrixEntityType.Lead },
|
{ label: 'Leads', id: BitrixEntityType.Lead },
|
||||||
// { label: 'Comments', id: BitrixEntityType.Comment },
|
{ label: 'Company', id: BitrixEntityType.Company },
|
||||||
{ label: 'Company', id: BitrixEntityType.Company }
|
{ label: 'Contacts', id: BitrixEntityType.Contact }
|
||||||
// { label: 'Activity', id: BitrixEntityType.Activity }
|
|
||||||
]
|
]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -242,7 +251,36 @@ export interface BitrixFieldMapping extends AttachedDoc {
|
|||||||
export interface BitrixActivity {
|
export interface BitrixActivity {
|
||||||
ID: string
|
ID: string
|
||||||
SUBJECT: string
|
SUBJECT: string
|
||||||
|
COMMUNICATIONS?: {
|
||||||
|
ENTITY_SETTINGS?: {
|
||||||
|
LAST_NAME: string
|
||||||
|
NAME: string
|
||||||
|
LEAD_TITLE: string
|
||||||
|
}
|
||||||
|
}[]
|
||||||
DESCRIPTION: string
|
DESCRIPTION: string
|
||||||
AUTHOR_ID: string
|
AUTHOR_ID: string
|
||||||
CREATED: number
|
CREATED: number
|
||||||
|
SETTINGS?: {
|
||||||
|
MESSAGE_HEADERS?: Record<string, string>
|
||||||
|
EMAIL_META?: Record<string, string>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type BitrixFiles = Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
authorId: string
|
||||||
|
authorName: string
|
||||||
|
date: string
|
||||||
|
id: number
|
||||||
|
image: boolean
|
||||||
|
name: string
|
||||||
|
size: number
|
||||||
|
type: string
|
||||||
|
urlDownload: string
|
||||||
|
urlShow: string
|
||||||
|
}
|
||||||
|
>
|
||||||
|
@ -57,7 +57,7 @@ export interface ConvertResult {
|
|||||||
mixins: Record<Ref<Mixin<Doc>>, Data<Doc>> // Mixins of document we will achive
|
mixins: Record<Ref<Mixin<Doc>>, Data<Doc>> // Mixins of document we will achive
|
||||||
extraDocs: Doc[] // Extra documents we will achive, etc.
|
extraDocs: Doc[] // Extra documents we will achive, etc.
|
||||||
extraSync: (AttachedDoc & BitrixSyncDoc)[] // Extra documents we will achive, etc.
|
extraSync: (AttachedDoc & BitrixSyncDoc)[] // Extra documents we will achive, etc.
|
||||||
blobs: [Attachment & BitrixSyncDoc, () => Promise<File | undefined>][]
|
blobs: [Attachment & BitrixSyncDoc, () => Promise<File | undefined>, (file: File, attach: Attachment) => void][]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -73,7 +73,7 @@ export async function convert (
|
|||||||
existingDoc: WithLookup<Doc> | undefined,
|
existingDoc: WithLookup<Doc> | undefined,
|
||||||
defaultCategories: TagCategory[],
|
defaultCategories: TagCategory[],
|
||||||
allTagElements: TagElement[],
|
allTagElements: TagElement[],
|
||||||
blobProvider?: (blobRef: any) => Promise<Blob | undefined>
|
blobProvider?: (blobRef: { file: string, id: string }) => Promise<Blob | undefined>
|
||||||
): Promise<ConvertResult> {
|
): Promise<ConvertResult> {
|
||||||
const hierarchy = client.getHierarchy()
|
const hierarchy = client.getHierarchy()
|
||||||
const bitrixId = `${rawDocument.ID as string}`
|
const bitrixId = `${rawDocument.ID as string}`
|
||||||
@ -93,7 +93,11 @@ export async function convert (
|
|||||||
|
|
||||||
const newExtraSyncDocs: (AttachedDoc & BitrixSyncDoc)[] = []
|
const newExtraSyncDocs: (AttachedDoc & BitrixSyncDoc)[] = []
|
||||||
const newExtraDocs: Doc[] = []
|
const newExtraDocs: Doc[] = []
|
||||||
const blobs: [Attachment & BitrixSyncDoc, () => Promise<File | undefined>][] = []
|
const blobs: [
|
||||||
|
Attachment & BitrixSyncDoc,
|
||||||
|
() => Promise<File | undefined>,
|
||||||
|
(file: File, attach: Attachment) => void
|
||||||
|
][] = []
|
||||||
const mixins: Record<Ref<Mixin<Doc>>, Data<Doc>> = {}
|
const mixins: Record<Ref<Mixin<Doc>>, Data<Doc>> = {}
|
||||||
|
|
||||||
const extractValue = (field?: string, alternatives?: string[]): any | undefined => {
|
const extractValue = (field?: string, alternatives?: string[]): any | undefined => {
|
||||||
@ -120,10 +124,12 @@ export async function convert (
|
|||||||
return lval.map((it) => it.VALUE)
|
return lval.map((it) => it.VALUE)
|
||||||
}
|
}
|
||||||
} else if (bfield.type === 'file') {
|
} else if (bfield.type === 'file') {
|
||||||
if (Array.isArray(lval)) {
|
if (Array.isArray(lval) && bfield.isMultiple) {
|
||||||
return lval.map((it) => ({ id: it.id, file: it.downloadUrl }))
|
return lval.map((it) => ({ id: it.id, file: it.downloadUrl }))
|
||||||
|
} else if (lval != null) {
|
||||||
|
return [{ id: lval.id, file: lval.downloadUrl }]
|
||||||
}
|
}
|
||||||
} else if (bfield.type === 'string' || bfield.type === 'url') {
|
} else if (bfield.type === 'string' || bfield.type === 'url' || bfield.type === 'crm_company') {
|
||||||
if (bfield.isMultiple && Array.isArray(lval)) {
|
if (bfield.isMultiple && Array.isArray(lval)) {
|
||||||
return lval.join(', ')
|
return lval.join(', ')
|
||||||
}
|
}
|
||||||
@ -365,6 +371,11 @@ export async function convert (
|
|||||||
return new File([response], fname, { type: response.type })
|
return new File([response], fname, { type: response.type })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
(file, attach) => {
|
||||||
|
attach.attachedTo = document._id
|
||||||
|
attach.size = file.size
|
||||||
|
attach.type = file.type
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,23 @@
|
|||||||
let classes: Ref<Class<Doc>>[] = []
|
let classes: Ref<Class<Doc>>[] = []
|
||||||
clQuery.query(core.class.Class, {}, (res) => {
|
clQuery.query(core.class.Class, {}, (res) => {
|
||||||
classes = filterDescendants(hierarchy, ofClass, res)
|
classes = filterDescendants(hierarchy, ofClass, res)
|
||||||
|
|
||||||
|
if (ofClass !== undefined) {
|
||||||
|
// We need to include all possible mixins as well
|
||||||
|
for (const ancestor of hierarchy.getAncestors(ofClass)) {
|
||||||
|
if (ancestor === ofClass) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const mixins = hierarchy.getDescendants(ancestor).filter((it) => hierarchy.isMixin(it))
|
||||||
|
for (const m of mixins) {
|
||||||
|
const mm = hierarchy.getClass(m)
|
||||||
|
if (!classes.includes(m) && mm.extends === ancestor && mm.label !== undefined) {
|
||||||
|
// Check if parent of
|
||||||
|
classes.push(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
import { Class, ClassifierKind, Doc, Mixin, Ref } from '@hcengineering/core'
|
import { Class, ClassifierKind, Doc, Mixin, Ref } from '@hcengineering/core'
|
||||||
import { getClient } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import setting from '@hcengineering/setting'
|
import setting from '@hcengineering/setting'
|
||||||
import { Label } from '@hcengineering/ui'
|
import { Icon, Label, tooltip } from '@hcengineering/ui'
|
||||||
import { getMixinStyle } from '../utils'
|
import { getMixinStyle } from '../utils'
|
||||||
|
|
||||||
export let value: Doc
|
export let value: Doc
|
||||||
@ -43,10 +43,8 @@
|
|||||||
mixins = hierarchy
|
mixins = hierarchy
|
||||||
.getDescendants(parentClass)
|
.getDescendants(parentClass)
|
||||||
.filter(
|
.filter(
|
||||||
(m) =>
|
(m) => hierarchy.getClass(m).kind === ClassifierKind.MIXIN && hierarchy.hasMixin(value, m)
|
||||||
hierarchy.getClass(m).kind === ClassifierKind.MIXIN &&
|
// && !hierarchy.hasMixin(hierarchy.getClass(m), setting.mixin.UserMixin)
|
||||||
hierarchy.hasMixin(value, m) &&
|
|
||||||
!hierarchy.hasMixin(hierarchy.getClass(m), setting.mixin.UserMixin)
|
|
||||||
)
|
)
|
||||||
.map((m) => hierarchy.getClass(m) as Mixin<Doc>)
|
.map((m) => hierarchy.getClass(m) as Mixin<Doc>)
|
||||||
}
|
}
|
||||||
@ -55,8 +53,19 @@
|
|||||||
{#if mixins.length > 0}
|
{#if mixins.length > 0}
|
||||||
<div class="mixin-container">
|
<div class="mixin-container">
|
||||||
{#each mixins as mixin}
|
{#each mixins as mixin}
|
||||||
<div class="mixin-selector" style={getMixinStyle(mixin._id, true)}>
|
{@const userMixin = hierarchy.hasMixin(mixin, setting.mixin.UserMixin)}
|
||||||
<Label label={mixin.label} />
|
<div class="mixin-selector" class:user-selector={userMixin} style={getMixinStyle(mixin._id, true)}>
|
||||||
|
{#if !userMixin}
|
||||||
|
<Label label={mixin.label} />
|
||||||
|
{:else}
|
||||||
|
<div use:tooltip={{ label: mixin.label }}>
|
||||||
|
{#if mixin.icon}
|
||||||
|
<Icon icon={mixin.icon} size={'small'} />
|
||||||
|
{:else}
|
||||||
|
Ⱞ
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@ -83,5 +92,8 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
.user-selector {
|
||||||
|
min-width: 24px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -51,6 +51,8 @@
|
|||||||
|
|
||||||
export let prefferedSorting: string = 'modifiedOn'
|
export let prefferedSorting: string = 'modifiedOn'
|
||||||
|
|
||||||
|
export let limit = 200
|
||||||
|
|
||||||
// If defined, will show a number of dummy items before real data will appear.
|
// If defined, will show a number of dummy items before real data will appear.
|
||||||
export let loadingProps: LoadingProps | undefined = undefined
|
export let loadingProps: LoadingProps | undefined = undefined
|
||||||
|
|
||||||
@ -92,6 +94,7 @@
|
|||||||
sortKey: string | string[],
|
sortKey: string | string[],
|
||||||
sortOrder: SortingOrder,
|
sortOrder: SortingOrder,
|
||||||
lookup: Lookup<Doc>,
|
lookup: Lookup<Doc>,
|
||||||
|
limit: number,
|
||||||
options?: FindOptions<Doc>
|
options?: FindOptions<Doc>
|
||||||
) {
|
) {
|
||||||
const sort = Array.isArray(sortKey)
|
const sort = Array.isArray(sortKey)
|
||||||
@ -114,13 +117,13 @@
|
|||||||
dispatch('content', objects)
|
dispatch('content', objects)
|
||||||
loading = loading === 1 ? 0 : -1
|
loading = loading === 1 ? 0 : -1
|
||||||
},
|
},
|
||||||
{ sort, limit: 200, ...options, lookup }
|
{ sort, limit, ...options, lookup }
|
||||||
)
|
)
|
||||||
if (update && ++loading > 0) {
|
if (update && ++loading > 0) {
|
||||||
objects = []
|
objects = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$: update(_class, query, _sortKey, sortOrder, lookup, options)
|
$: update(_class, query, _sortKey, sortOrder, lookup, limit, options)
|
||||||
|
|
||||||
const showMenu = async (ev: MouseEvent, object: Doc, row: number): Promise<void> => {
|
const showMenu = async (ev: MouseEvent, object: Doc, row: number): Promise<void> => {
|
||||||
selection = row
|
selection = row
|
||||||
@ -366,7 +369,15 @@
|
|||||||
<div class="content" class:padding={showNotification || enableChecking}>
|
<div class="content" class:padding={showNotification || enableChecking}>
|
||||||
<Label label={view.string.Total} />: {total}
|
<Label label={view.string.Total} />: {total}
|
||||||
{#if objects.length > 0 && objects.length < total}
|
{#if objects.length > 0 && objects.length < total}
|
||||||
<Label label={view.string.Shown} />: {objects.length}
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<div
|
||||||
|
class="cursor-pointer ml-2"
|
||||||
|
on:click={() => {
|
||||||
|
limit = limit + 100
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label label={view.string.Shown} />: {objects.length}
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user