mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 21:50:34 +03:00
TSK-1015: Bitrix Create Vacancy/Application (#2913)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
ef836ac2f0
commit
921291ba24
@ -53,7 +53,9 @@
|
|||||||
"qs": "~6.11.0",
|
"qs": "~6.11.0",
|
||||||
"@hcengineering/tags": "^0.6.3",
|
"@hcengineering/tags": "^0.6.3",
|
||||||
"@hcengineering/tags-resources": "^0.6.0",
|
"@hcengineering/tags-resources": "^0.6.0",
|
||||||
"fast-equals": "^2.0.3"
|
"fast-equals": "^2.0.3",
|
||||||
|
"@hcengineering/recruit": "^0.6.8",
|
||||||
|
"@hcengineering/task": "^0.6.3"
|
||||||
},
|
},
|
||||||
"repository": "https://github.com/hcengineering/anticrm",
|
"repository": "https://github.com/hcengineering/anticrm",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
@ -70,6 +70,12 @@
|
|||||||
action: (_: any, evt: MouseEvent) => {
|
action: (_: any, evt: MouseEvent) => {
|
||||||
addMapping(evt, MappingOperation.FindReference)
|
addMapping(evt, MappingOperation.FindReference)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: getEmbeddedLabel('Create Vacancy and application'),
|
||||||
|
action: (_: any, evt: MouseEvent) => {
|
||||||
|
addMapping(evt, MappingOperation.CreateHRApplication)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
] as Action[]
|
] as Action[]
|
||||||
</script>
|
</script>
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import { Label } from '@hcengineering/ui'
|
import { Label } from '@hcengineering/ui'
|
||||||
import bitrix from '../plugin'
|
import bitrix from '../plugin'
|
||||||
import CopyMapping from './mappings/CopyMapping.svelte'
|
import CopyMapping from './mappings/CopyMapping.svelte'
|
||||||
|
import CreateAttachedDocMapping from './mappings/CreateHRApplicationMapping.svelte'
|
||||||
import CreateChannelMapping from './mappings/CreateChannelMapping.svelte'
|
import CreateChannelMapping from './mappings/CreateChannelMapping.svelte'
|
||||||
import CreateTagMapping from './mappings/CreateTagMapping.svelte'
|
import CreateTagMapping from './mappings/CreateTagMapping.svelte'
|
||||||
import DownloadAttachmentMapping from './mappings/DownloadAttachmentMapping.svelte'
|
import DownloadAttachmentMapping from './mappings/DownloadAttachmentMapping.svelte'
|
||||||
@ -45,5 +46,7 @@
|
|||||||
<DownloadAttachmentMapping {mapping} {fields} {attribute} {field} bind:this={op} />
|
<DownloadAttachmentMapping {mapping} {fields} {attribute} {field} bind:this={op} />
|
||||||
{:else if _kind === MappingOperation.FindReference}
|
{:else if _kind === MappingOperation.FindReference}
|
||||||
<FindReferenceMapping {mapping} {fields} {attribute} {field} bind:this={op} />
|
<FindReferenceMapping {mapping} {fields} {attribute} {field} bind:this={op} />
|
||||||
|
{:else if _kind === MappingOperation.CreateHRApplication}
|
||||||
|
<CreateAttachedDocMapping {mapping} {fields} {attribute} {field} bind:this={op} />
|
||||||
{/if}
|
{/if}
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
import { getClient } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import { Button, Icon, IconArrowLeft, IconClose, Label } from '@hcengineering/ui'
|
import { Button, Icon, IconArrowLeft, IconClose, Label } from '@hcengineering/ui'
|
||||||
import CopyMappingPresenter from './mappings/CopyMappingPresenter.svelte'
|
import CopyMappingPresenter from './mappings/CopyMappingPresenter.svelte'
|
||||||
|
import CreateAttachedDocPresenter from './mappings/CreateHRApplicationPresenter.svelte'
|
||||||
import CreateChannelMappingPresenter from './mappings/CreateChannelMappingPresenter.svelte'
|
import CreateChannelMappingPresenter from './mappings/CreateChannelMappingPresenter.svelte'
|
||||||
import CreateTagMappingPresenter from './mappings/CreateTagMappingPresenter.svelte'
|
import CreateTagMappingPresenter from './mappings/CreateTagMappingPresenter.svelte'
|
||||||
import DownloadAttachmentPresenter from './mappings/DownloadAttachmentPresenter.svelte'
|
import DownloadAttachmentPresenter from './mappings/DownloadAttachmentPresenter.svelte'
|
||||||
@ -40,6 +41,8 @@
|
|||||||
<DownloadAttachmentPresenter {mapping} {value} />
|
<DownloadAttachmentPresenter {mapping} {value} />
|
||||||
{:else if kind === MappingOperation.FindReference}
|
{:else if kind === MappingOperation.FindReference}
|
||||||
<FindReferencePresenter {mapping} {value} />
|
<FindReferencePresenter {mapping} {value} />
|
||||||
|
{:else if kind === MappingOperation.CreateHRApplication}
|
||||||
|
<CreateAttachedDocPresenter {mapping} {value} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
@ -0,0 +1,95 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
BitrixEntityMapping,
|
||||||
|
BitrixFieldMapping,
|
||||||
|
CreateHRApplication,
|
||||||
|
Fields,
|
||||||
|
MappingOperation
|
||||||
|
} from '@hcengineering/bitrix'
|
||||||
|
import { AnyAttribute } from '@hcengineering/core'
|
||||||
|
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||||
|
import { getClient } from '@hcengineering/presentation'
|
||||||
|
import task from '@hcengineering/task'
|
||||||
|
import { DropdownLabels, DropdownTextItem } from '@hcengineering/ui'
|
||||||
|
import { ObjectBox } from '@hcengineering/view-resources'
|
||||||
|
import bitrix from '../../plugin'
|
||||||
|
import recruit from '@hcengineering/recruit'
|
||||||
|
|
||||||
|
export let mapping: BitrixEntityMapping
|
||||||
|
export let fields: Fields = {}
|
||||||
|
export let attribute: AnyAttribute
|
||||||
|
export let field: BitrixFieldMapping | undefined
|
||||||
|
|
||||||
|
let stateField = (field?.operation as CreateHRApplication)?.stateField
|
||||||
|
let vacancyField = (field?.operation as CreateHRApplication)?.vacancyField
|
||||||
|
let defaultTemplate = (field?.operation as CreateHRApplication)?.defaultTemplate
|
||||||
|
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
|
export async function save (): Promise<void> {
|
||||||
|
if (field !== undefined) {
|
||||||
|
await client.update(field, {
|
||||||
|
operation: {
|
||||||
|
kind: MappingOperation.CreateHRApplication,
|
||||||
|
stateField,
|
||||||
|
vacancyField,
|
||||||
|
defaultTemplate
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await client.addCollection(bitrix.class.FieldMapping, mapping.space, mapping._id, mapping._class, 'fields', {
|
||||||
|
ofClass: attribute.attributeOf,
|
||||||
|
attributeName: attribute.name,
|
||||||
|
operation: {
|
||||||
|
kind: MappingOperation.CreateHRApplication,
|
||||||
|
stateField,
|
||||||
|
vacancyField,
|
||||||
|
defaultTemplate
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getItems (fields: Fields): DropdownTextItem[] {
|
||||||
|
return Object.entries(fields).map((it) => ({
|
||||||
|
id: it[0],
|
||||||
|
label: `${it[1].formLabel ?? it[1].title}${it[0].startsWith('UF_') ? ' *' : ''}`
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
$: items = getItems(fields)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex-col flex-wrap">
|
||||||
|
<div class="flex-row-center gap-2">
|
||||||
|
<div class="flex-col w-120">
|
||||||
|
<DropdownLabels minW0={false} label={getEmbeddedLabel('Vacancy field')} {items} bind:selected={vacancyField} />
|
||||||
|
<DropdownLabels minW0={false} label={getEmbeddedLabel('State field')} {items} bind:selected={stateField} />
|
||||||
|
<ObjectBox
|
||||||
|
label={getEmbeddedLabel('Template')}
|
||||||
|
searchField={'title'}
|
||||||
|
_class={task.class.KanbanTemplate}
|
||||||
|
docQuery={{ space: recruit.space.VacancyTemplates }}
|
||||||
|
bind:value={defaultTemplate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.pattern {
|
||||||
|
margin: 0.5rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border: 1px dashed var(--accent-color);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
|
||||||
|
// text-transform: uppercase;
|
||||||
|
color: var(--accent-color);
|
||||||
|
&:hover {
|
||||||
|
color: var(--caption-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,37 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { BitrixEntityMapping, BitrixFieldMapping, CreateHRApplication } from '@hcengineering/bitrix'
|
||||||
|
import task from '@hcengineering/task'
|
||||||
|
import { ObjectPresenter } from '@hcengineering/view-resources'
|
||||||
|
|
||||||
|
export let mapping: BitrixEntityMapping
|
||||||
|
export let value: BitrixFieldMapping
|
||||||
|
|
||||||
|
$: op = value.operation as CreateHRApplication
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap">
|
||||||
|
<div class="pattern flex-row-center gap-2">
|
||||||
|
{mapping.bitrixFields[op.vacancyField]?.filterLabel} -> {mapping.bitrixFields[op.stateField]?.filterLabel}
|
||||||
|
<span class="p-1">-></span>
|
||||||
|
<ObjectPresenter objectId={op.defaultTemplate} _class={task.class.KanbanTemplate} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.pattern {
|
||||||
|
margin: 0.1rem;
|
||||||
|
padding: 0.3rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border: 1px dashed var(--accent-color);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
|
||||||
|
// text-transform: uppercase;
|
||||||
|
color: var(--accent-color);
|
||||||
|
&:hover {
|
||||||
|
color: var(--caption-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -36,7 +36,9 @@
|
|||||||
"@hcengineering/attachment": "^0.6.1",
|
"@hcengineering/attachment": "^0.6.1",
|
||||||
"fast-equals": "^2.0.3",
|
"fast-equals": "^2.0.3",
|
||||||
"qs": "~6.11.0",
|
"qs": "~6.11.0",
|
||||||
"@hcengineering/gmail": "^0.6.4"
|
"@hcengineering/gmail": "^0.6.4",
|
||||||
|
"@hcengineering/recruit": "^0.6.8",
|
||||||
|
"@hcengineering/task": "^0.6.3"
|
||||||
},
|
},
|
||||||
"repository": "https://github.com/hcengineering/anticrm",
|
"repository": "https://github.com/hcengineering/anticrm",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
72
plugins/bitrix/src/hr.ts
Normal file
72
plugins/bitrix/src/hr.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { Organization } from '@hcengineering/contact'
|
||||||
|
import core, { Account, Client, Doc, Ref, SortingOrder, TxOperations } from '@hcengineering/core'
|
||||||
|
import recruit, { Vacancy } from '@hcengineering/recruit'
|
||||||
|
import task, { KanbanTemplate, State, calcRank, createKanban } from '@hcengineering/task'
|
||||||
|
|
||||||
|
export async function createVacancy (
|
||||||
|
rawClient: Client,
|
||||||
|
name: string,
|
||||||
|
templateId: Ref<KanbanTemplate>,
|
||||||
|
account: Ref<Account>,
|
||||||
|
company?: Ref<Organization>
|
||||||
|
): Promise<Ref<Vacancy>> {
|
||||||
|
const client = new TxOperations(rawClient, account)
|
||||||
|
const template = await client.findOne(task.class.KanbanTemplate, { _id: templateId })
|
||||||
|
if (template === undefined) {
|
||||||
|
throw Error(`Failed to find target kanban template: ${templateId}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const sequence = await client.findOne(task.class.Sequence, { attachedTo: recruit.class.Vacancy })
|
||||||
|
if (sequence === undefined) {
|
||||||
|
throw new Error('sequence object not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
const incResult = await client.update(sequence, { $inc: { sequence: 1 } }, true)
|
||||||
|
|
||||||
|
const id = await client.createDoc(recruit.class.Vacancy, core.space.Space, {
|
||||||
|
name,
|
||||||
|
description: template.shortDescription ?? '',
|
||||||
|
fullDescription: template.description,
|
||||||
|
private: false,
|
||||||
|
archived: false,
|
||||||
|
company,
|
||||||
|
number: (incResult as any).object.sequence,
|
||||||
|
members: []
|
||||||
|
})
|
||||||
|
|
||||||
|
await createKanban(client, id, templateId)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createApplication (
|
||||||
|
client: TxOperations,
|
||||||
|
selectedState: State,
|
||||||
|
_space: Ref<Vacancy>,
|
||||||
|
doc: Doc
|
||||||
|
): Promise<void> {
|
||||||
|
if (selectedState === undefined) {
|
||||||
|
throw new Error(`Please select initial state:${_space}`)
|
||||||
|
}
|
||||||
|
const state = await client.findOne(task.class.State, { space: _space, _id: selectedState?._id })
|
||||||
|
if (state === undefined) {
|
||||||
|
throw new Error(`create application: state not found space:${_space}`)
|
||||||
|
}
|
||||||
|
const sequence = await client.findOne(task.class.Sequence, { attachedTo: recruit.class.Applicant })
|
||||||
|
if (sequence === undefined) {
|
||||||
|
throw new Error('sequence object not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastOne = await client.findOne(recruit.class.Applicant, {}, { sort: { rank: SortingOrder.Descending } })
|
||||||
|
const incResult = await client.update(sequence, { $inc: { sequence: 1 } }, true)
|
||||||
|
|
||||||
|
await client.addCollection(recruit.class.Applicant, _space, doc._id, recruit.mixin.Candidate, 'applications', {
|
||||||
|
state: state._id,
|
||||||
|
doneState: null,
|
||||||
|
number: (incResult as any).object.sequence,
|
||||||
|
assignee: null,
|
||||||
|
rank: calcRank(lastOne, undefined),
|
||||||
|
startDate: null,
|
||||||
|
dueDate: null,
|
||||||
|
createOn: Date.now()
|
||||||
|
})
|
||||||
|
}
|
@ -123,20 +123,19 @@ export async function syncDocument (
|
|||||||
|
|
||||||
// Just create supplier documents, like TagElements.
|
// Just create supplier documents, like TagElements.
|
||||||
for (const ed of resultDoc.extraDocs) {
|
for (const ed of resultDoc.extraDocs) {
|
||||||
await applyOp.createDoc(
|
const { _class, space, _id, ...data } = ed
|
||||||
ed._class,
|
await applyOp.createDoc(_class, space, data, _id, resultDoc.document.modifiedOn, resultDoc.document.modifiedBy)
|
||||||
ed.space,
|
|
||||||
ed,
|
|
||||||
ed._id,
|
|
||||||
resultDoc.document.modifiedOn,
|
|
||||||
resultDoc.document.modifiedBy
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const op of resultDoc.postOperations) {
|
||||||
|
await op(resultDoc.document, existing)
|
||||||
|
}
|
||||||
|
|
||||||
|
const idMapping = new Map<Ref<Doc>, Ref<Doc>>()
|
||||||
|
|
||||||
// Find all attachment documents to existing.
|
// Find all attachment documents to existing.
|
||||||
const byClass = new Map<Ref<Class<Doc>>, (AttachedDoc & BitrixSyncDoc)[]>()
|
const byClass = new Map<Ref<Class<Doc>>, (AttachedDoc & BitrixSyncDoc)[]>()
|
||||||
|
|
||||||
const idMapping = new Map<Ref<Doc>, Ref<Doc>>()
|
|
||||||
for (const d of resultDoc.extraSync) {
|
for (const d of resultDoc.extraSync) {
|
||||||
byClass.set(d._class, [...(byClass.get(d._class) ?? []), d])
|
byClass.set(d._class, [...(byClass.get(d._class) ?? []), d])
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { ChannelProvider } from '@hcengineering/contact'
|
import { ChannelProvider } from '@hcengineering/contact'
|
||||||
import { AttachedDoc, Class, Doc, Mixin, Ref } from '@hcengineering/core'
|
import { AttachedDoc, Class, Doc, Mixin, Ref } from '@hcengineering/core'
|
||||||
import { ExpertKnowledge, InitialKnowledge, MeaningfullKnowledge } from '@hcengineering/tags'
|
import { ExpertKnowledge, InitialKnowledge, MeaningfullKnowledge } from '@hcengineering/tags'
|
||||||
|
import { KanbanTemplate } from '@hcengineering/task'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -174,7 +175,8 @@ export enum MappingOperation {
|
|||||||
CreateTag, // Create tag
|
CreateTag, // Create tag
|
||||||
CreateChannel, // Create channel
|
CreateChannel, // Create channel
|
||||||
DownloadAttachment,
|
DownloadAttachment,
|
||||||
FindReference
|
FindReference,
|
||||||
|
CreateHRApplication
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -252,6 +254,35 @@ export interface FindReferenceOperation {
|
|||||||
referenceClass: Ref<Class<Doc>>
|
referenceClass: Ref<Class<Doc>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface CreateAttachedField {
|
||||||
|
match: boolean // We should match type and pass if exists.
|
||||||
|
|
||||||
|
// Original document field to use value from.
|
||||||
|
sourceField: string
|
||||||
|
valueField: string // final value should go into valueField, field name to match, like `space`
|
||||||
|
|
||||||
|
// If reference is defined, we should find for some existing document by matching field with sourceField value.
|
||||||
|
// Document we should match value against.
|
||||||
|
referenceClass: Ref<Class<Doc>>
|
||||||
|
// Field to check for matched value against.
|
||||||
|
referenceField?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface CreateHRApplication {
|
||||||
|
kind: MappingOperation.CreateHRApplication
|
||||||
|
|
||||||
|
vacancyField: string // Name of vacancy in bitrix.
|
||||||
|
stateField: string // Name of status in bitrix.
|
||||||
|
|
||||||
|
defaultTemplate: Ref<KanbanTemplate>
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -265,6 +296,7 @@ export interface BitrixFieldMapping extends AttachedDoc {
|
|||||||
| CreateChannelOperation
|
| CreateChannelOperation
|
||||||
| DownloadAttachmentOperation
|
| DownloadAttachmentOperation
|
||||||
| FindReferenceOperation
|
| FindReferenceOperation
|
||||||
|
| CreateHRApplication
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||||
import contact, { Channel, EmployeeAccount } from '@hcengineering/contact'
|
import contact, { Channel, EmployeeAccount, Organization } from '@hcengineering/contact'
|
||||||
import core, {
|
import core, {
|
||||||
AnyAttribute,
|
AnyAttribute,
|
||||||
AttachedDoc,
|
AttachedDoc,
|
||||||
@ -7,14 +7,18 @@ import core, {
|
|||||||
Client,
|
Client,
|
||||||
Data,
|
Data,
|
||||||
Doc,
|
Doc,
|
||||||
generateId,
|
|
||||||
Mixin,
|
Mixin,
|
||||||
Ref,
|
Ref,
|
||||||
|
RefTo,
|
||||||
Space,
|
Space,
|
||||||
WithLookup
|
TxOperations,
|
||||||
|
WithLookup,
|
||||||
|
generateId
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { Message } from '@hcengineering/gmail'
|
import { Message } from '@hcengineering/gmail'
|
||||||
|
import recruit, { Candidate, Vacancy } from '@hcengineering/recruit'
|
||||||
import tags, { TagCategory, TagElement, TagReference } from '@hcengineering/tags'
|
import tags, { TagCategory, TagElement, TagReference } from '@hcengineering/tags'
|
||||||
|
import task from '@hcengineering/task'
|
||||||
import bitrix, {
|
import bitrix, {
|
||||||
BitrixEntityMapping,
|
BitrixEntityMapping,
|
||||||
BitrixEntityType,
|
BitrixEntityType,
|
||||||
@ -22,11 +26,13 @@ import bitrix, {
|
|||||||
BitrixSyncDoc,
|
BitrixSyncDoc,
|
||||||
CopyValueOperation,
|
CopyValueOperation,
|
||||||
CreateChannelOperation,
|
CreateChannelOperation,
|
||||||
|
CreateHRApplication,
|
||||||
CreateTagOperation,
|
CreateTagOperation,
|
||||||
DownloadAttachmentOperation,
|
DownloadAttachmentOperation,
|
||||||
FindReferenceOperation,
|
FindReferenceOperation,
|
||||||
MappingOperation
|
MappingOperation
|
||||||
} from '.'
|
} from '.'
|
||||||
|
import { createApplication, createVacancy } from './hr'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -60,6 +66,11 @@ export interface BitrixSyncRequest {
|
|||||||
update: (doc: Ref<Doc>) => void
|
update: (doc: Ref<Doc>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type PostOperation = (doc: BitrixSyncDoc, existing?: Doc) => Promise<void>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -71,6 +82,7 @@ export interface ConvertResult {
|
|||||||
gmailDocuments: (Message & BitrixSyncDoc)[]
|
gmailDocuments: (Message & BitrixSyncDoc)[]
|
||||||
blobs: [Attachment & BitrixSyncDoc, () => Promise<File | undefined>, (file: File, attach: Attachment) => void][]
|
blobs: [Attachment & BitrixSyncDoc, () => Promise<File | undefined>, (file: File, attach: Attachment) => void][]
|
||||||
syncRequests: BitrixSyncRequest[]
|
syncRequests: BitrixSyncRequest[]
|
||||||
|
postOperations: PostOperation[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -113,6 +125,8 @@ export async function convert (
|
|||||||
][] = []
|
][] = []
|
||||||
const mixins: Record<Ref<Mixin<Doc>>, Data<Doc>> = {}
|
const mixins: Record<Ref<Mixin<Doc>>, Data<Doc>> = {}
|
||||||
|
|
||||||
|
const postOperations: PostOperation[] = []
|
||||||
|
|
||||||
// Fill required mixins.
|
// Fill required mixins.
|
||||||
for (const m of entity.mixins ?? []) {
|
for (const m of entity.mixins ?? []) {
|
||||||
mixins[m] = {}
|
mixins[m] = {}
|
||||||
@ -383,6 +397,65 @@ export async function convert (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getCreateAttachedValue = async (attr: AnyAttribute, operation: CreateHRApplication): Promise<void> => {
|
||||||
|
const vacancyName = extractValue(operation.vacancyField)
|
||||||
|
const statusName = extractValue(operation.stateField)
|
||||||
|
|
||||||
|
postOperations.push(async (doc, existingDoc) => {
|
||||||
|
const vacancies = await client.findAll(recruit.class.Vacancy, {})
|
||||||
|
|
||||||
|
let vacancyId: Ref<Vacancy> | undefined
|
||||||
|
|
||||||
|
if (vacancyName !== undefined) {
|
||||||
|
const tName = vacancyName.trim().toLowerCase()
|
||||||
|
const vacancy = vacancies.find((it) => it.name.toLowerCase().trim() === tName)
|
||||||
|
|
||||||
|
let refOrgField: Ref<Organization> | undefined
|
||||||
|
const allAttrs = hierarchy.getAllAttributes(recruit.mixin.Candidate)
|
||||||
|
for (const a of allAttrs.values()) {
|
||||||
|
if (a.type._class === core.class.RefTo && (a.type as RefTo<Doc>).to === contact.class.Organization) {
|
||||||
|
refOrgField = (mixins as any)[recruit.mixin.Candidate][a.name] as Ref<Organization>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vacancy !== undefined) {
|
||||||
|
vacancyId = vacancy?._id
|
||||||
|
} else {
|
||||||
|
vacancyId = await createVacancy(
|
||||||
|
client,
|
||||||
|
vacancyName.trim(),
|
||||||
|
operation.defaultTemplate,
|
||||||
|
document.modifiedBy,
|
||||||
|
refOrgField
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if candidate already have vacancy
|
||||||
|
const existing = await client.findOne(recruit.class.Applicant, {
|
||||||
|
attachedTo: (existingDoc?._id ?? doc._id) as unknown as Ref<Candidate>,
|
||||||
|
space: vacancyId
|
||||||
|
})
|
||||||
|
|
||||||
|
if (statusName != null && statusName !== '') {
|
||||||
|
// Find status for vacancy
|
||||||
|
const states = await client.findAll(task.class.State, { space: vacancyId })
|
||||||
|
|
||||||
|
const state = states.find((it) => it.name.toLowerCase().trim() === statusName.toLowerCase().trim())
|
||||||
|
const ops = new TxOperations(client, document.modifiedBy)
|
||||||
|
if (state !== undefined) {
|
||||||
|
if (existing !== undefined && existing.state !== state?._id) {
|
||||||
|
await ops.update(existing, { state: state._id })
|
||||||
|
} else {
|
||||||
|
await createApplication(ops, state, vacancyId, document)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const setValue = (value: any, attr: AnyAttribute): void => {
|
const setValue = (value: any, attr: AnyAttribute): void => {
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
if (hierarchy.isMixin(attr.attributeOf)) {
|
if (hierarchy.isMixin(attr.attributeOf)) {
|
||||||
@ -479,6 +552,10 @@ export async function convert (
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case MappingOperation.CreateHRApplication: {
|
||||||
|
await getCreateAttachedValue(attr, f.operation)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setValue(value, attr)
|
setValue(value, attr)
|
||||||
}
|
}
|
||||||
@ -490,7 +567,8 @@ export async function convert (
|
|||||||
extraDocs: newExtraDocs,
|
extraDocs: newExtraDocs,
|
||||||
blobs,
|
blobs,
|
||||||
syncRequests,
|
syncRequests,
|
||||||
gmailDocuments: []
|
gmailDocuments: [],
|
||||||
|
postOperations
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +107,9 @@
|
|||||||
"Organizations": "Companies",
|
"Organizations": "Companies",
|
||||||
|
|
||||||
"TemplateReplace": "You you replace selected template?",
|
"TemplateReplace": "You you replace selected template?",
|
||||||
"TemplateReplaceConfirm": "All field changes will be override by template values"
|
"TemplateReplaceConfirm": "All field changes will be override by template values",
|
||||||
|
|
||||||
|
"OpenVacancyList": "Open list"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"TalentRequired": "Please select talent",
|
"TalentRequired": "Please select talent",
|
||||||
|
@ -108,7 +108,8 @@
|
|||||||
"SearchVacancy": "Найти вакансию...",
|
"SearchVacancy": "Найти вакансию...",
|
||||||
"Organizations": "Компании",
|
"Organizations": "Компании",
|
||||||
"TemplateReplace": "Вы хотите заменить выбранный шаблон?",
|
"TemplateReplace": "Вы хотите заменить выбранный шаблон?",
|
||||||
"TemplateReplaceConfirm": "Все внесенные изменения в будут заменены значениями из шаблоном"
|
"TemplateReplaceConfirm": "Все внесенные изменения в будут заменены значениями из шаблоном",
|
||||||
|
"OpenVacancyList": "Открыть список"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"TalentRequired": "Пожалуйста выберите таланта",
|
"TalentRequired": "Пожалуйста выберите таланта",
|
||||||
|
@ -13,16 +13,17 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import core, { FindOptions, Ref, SortingOrder, Space } from '@hcengineering/core'
|
import core, { FindOptions, SortingOrder } from '@hcengineering/core'
|
||||||
import { createQuery } from '@hcengineering/presentation'
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
import { Applicant } from '@hcengineering/recruit'
|
import { Applicant, Vacancy } from '@hcengineering/recruit'
|
||||||
import task from '@hcengineering/task'
|
import task from '@hcengineering/task'
|
||||||
import { Loading } from '@hcengineering/ui'
|
import { Button, Label, Loading } from '@hcengineering/ui'
|
||||||
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
|
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
|
||||||
import { Table } from '@hcengineering/view-resources'
|
import { DocNavLink, ObjectPresenter, Table } from '@hcengineering/view-resources'
|
||||||
import recruit from '../plugin'
|
import recruit from '../plugin'
|
||||||
|
|
||||||
export let value: Ref<Space>
|
export let value: Vacancy
|
||||||
|
export let openList: () => void
|
||||||
|
|
||||||
const options: FindOptions<Applicant> = {
|
const options: FindOptions<Applicant> = {
|
||||||
lookup: {
|
lookup: {
|
||||||
@ -62,13 +63,27 @@
|
|||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-between flex-grow p-1 mb-4">
|
||||||
|
<div class="fs-title flex-row-center">
|
||||||
|
<Label label={recruit.string.Applications} />
|
||||||
|
<div class="ml-2">
|
||||||
|
<Button label={recruit.string.OpenVacancyList} on:click={openList} size={'small'} kind={'link-bordered'} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-row-center">
|
||||||
|
<DocNavLink object={value}>
|
||||||
|
<ObjectPresenter _class={value._class} objectId={value._id} {value} />
|
||||||
|
</DocNavLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="popup-table">
|
<div class="popup-table">
|
||||||
{#if viewlet && !loading}
|
{#if viewlet && !loading}
|
||||||
<Table
|
<Table
|
||||||
_class={recruit.class.Applicant}
|
_class={recruit.class.Applicant}
|
||||||
config={preference?.config ?? viewlet.config}
|
config={preference?.config ?? viewlet.config}
|
||||||
|
query={{ space: value._id }}
|
||||||
{options}
|
{options}
|
||||||
query={{ space: value }}
|
|
||||||
loadingProps={{ length: 0 }}
|
loadingProps={{ length: 0 }}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
|
@ -14,8 +14,8 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Ref } from '@hcengineering/core'
|
import { Ref } from '@hcengineering/core'
|
||||||
import { Vacancy, recruitId } from '@hcengineering/recruit'
|
import { recruitId, Vacancy } from '@hcengineering/recruit'
|
||||||
import { Icon, getCurrentLocation, navigate, tooltip } from '@hcengineering/ui'
|
import { closeTooltip, getCurrentLocation, Icon, navigate, tooltip } from '@hcengineering/ui'
|
||||||
import recruit from '../plugin'
|
import recruit from '../plugin'
|
||||||
import VacancyApplicationsPopup from './VacancyApplicationsPopup.svelte'
|
import VacancyApplicationsPopup from './VacancyApplicationsPopup.svelte'
|
||||||
|
|
||||||
@ -23,24 +23,27 @@
|
|||||||
export let applications: Map<Ref<Vacancy>, { count: number; modifiedOn: number }> | undefined
|
export let applications: Map<Ref<Vacancy>, { count: number; modifiedOn: number }> | undefined
|
||||||
|
|
||||||
function click () {
|
function click () {
|
||||||
|
closeTooltip()
|
||||||
const loc = getCurrentLocation()
|
const loc = getCurrentLocation()
|
||||||
loc.fragment = undefined
|
loc.fragment = undefined
|
||||||
loc.query = undefined
|
loc.query = undefined
|
||||||
loc.path[2] = recruitId
|
loc.path[2] = recruitId
|
||||||
loc.path[3] = value._id
|
loc.path[3] = value._id
|
||||||
|
loc.path.length = 4
|
||||||
navigate(loc)
|
navigate(loc)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if value}
|
{#if value}
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div
|
<div
|
||||||
class="sm-tool-icon"
|
class="sm-tool-icon"
|
||||||
use:tooltip={{
|
use:tooltip={{
|
||||||
label: recruit.string.Applications,
|
// label: recruit.string.Applications,
|
||||||
component: VacancyApplicationsPopup,
|
component: VacancyApplicationsPopup,
|
||||||
props: { value: value._id }
|
props: { value, openList: click }
|
||||||
}}
|
}}
|
||||||
on:click={click}
|
on:click|stopPropagation|preventDefault={click}
|
||||||
>
|
>
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<Icon icon={recruit.icon.Application} size={'small'} />
|
<Icon icon={recruit.icon.Application} size={'small'} />
|
||||||
|
@ -125,7 +125,8 @@ export default mergeIds(recruitId, recruit, {
|
|||||||
Application: '' as IntlString,
|
Application: '' as IntlString,
|
||||||
|
|
||||||
TemplateReplace: '' as IntlString,
|
TemplateReplace: '' as IntlString,
|
||||||
TemplateReplaceConfirm: '' as IntlString
|
TemplateReplaceConfirm: '' as IntlString,
|
||||||
|
OpenVacancyList: '' as IntlString
|
||||||
},
|
},
|
||||||
space: {
|
space: {
|
||||||
CandidatesPublic: '' as Ref<Space>
|
CandidatesPublic: '' as Ref<Space>
|
||||||
|
Loading…
Reference in New Issue
Block a user