2021-08-07 17:03:06 +00:00
|
|
|
<!--
|
|
|
|
// Copyright © 2020 Anticrm Platform Contributors.
|
|
|
|
//
|
|
|
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License. You may
|
|
|
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
//
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
-->
|
|
|
|
<script lang="ts">
|
2022-10-05 06:41:02 +00:00
|
|
|
import { AttachmentStyledBox } from '@hcengineering/attachment-resources'
|
2022-09-21 08:08:25 +00:00
|
|
|
import contact, { Organization } from '@hcengineering/contact'
|
2023-03-22 02:48:57 +00:00
|
|
|
import { UserBox } from '@hcengineering/contact-resources'
|
2023-03-21 16:08:45 +00:00
|
|
|
import core, {
|
|
|
|
Data,
|
|
|
|
fillDefaults,
|
|
|
|
FindResult,
|
|
|
|
generateId,
|
|
|
|
getCurrentAccount,
|
|
|
|
Ref,
|
|
|
|
SortingOrder
|
|
|
|
} from '@hcengineering/core'
|
2023-03-22 02:48:57 +00:00
|
|
|
import { Card, createQuery, getClient, InlineAttributeBar, MessageBox } from '@hcengineering/presentation'
|
2023-01-31 05:48:40 +00:00
|
|
|
import { Vacancy as VacancyClass } from '@hcengineering/recruit'
|
2023-02-17 09:26:44 +00:00
|
|
|
import tags from '@hcengineering/tags'
|
2022-09-21 08:08:25 +00:00
|
|
|
import task, { createKanban, KanbanTemplate } from '@hcengineering/task'
|
2023-03-17 06:17:53 +00:00
|
|
|
import tracker, {
|
|
|
|
calcRank,
|
|
|
|
Issue,
|
|
|
|
IssueStatus,
|
|
|
|
IssueTemplate,
|
|
|
|
IssueTemplateData,
|
|
|
|
Project
|
|
|
|
} from '@hcengineering/tracker'
|
2023-03-17 15:57:36 +00:00
|
|
|
import {
|
|
|
|
Button,
|
|
|
|
Component,
|
|
|
|
createFocusManager,
|
|
|
|
EditBox,
|
|
|
|
FocusHandler,
|
|
|
|
IconAttachment,
|
|
|
|
showPopup
|
|
|
|
} from '@hcengineering/ui'
|
2022-02-09 09:08:09 +00:00
|
|
|
import { createEventDispatcher } from 'svelte'
|
|
|
|
import recruit from '../plugin'
|
2022-05-16 11:41:22 +00:00
|
|
|
import Company from './icons/Company.svelte'
|
2021-11-02 08:45:08 +00:00
|
|
|
import Vacancy from './icons/Vacancy.svelte'
|
2021-08-07 17:03:06 +00:00
|
|
|
|
|
|
|
const dispatch = createEventDispatcher()
|
|
|
|
|
|
|
|
let name: string = ''
|
2022-11-09 11:24:02 +00:00
|
|
|
let template: KanbanTemplate | undefined
|
2021-12-14 09:24:14 +00:00
|
|
|
let templateId: Ref<KanbanTemplate> | undefined
|
2023-03-21 04:43:51 +00:00
|
|
|
let appliedTemplateId: Ref<KanbanTemplate> | undefined
|
2022-10-05 06:41:02 +00:00
|
|
|
let objectId: Ref<VacancyClass> = generateId()
|
2022-11-09 11:24:02 +00:00
|
|
|
let issueTemplates: FindResult<IssueTemplate>
|
2022-10-05 06:41:02 +00:00
|
|
|
|
2023-03-17 15:57:36 +00:00
|
|
|
let fullDescription: string = ''
|
2023-01-31 05:48:40 +00:00
|
|
|
|
2022-09-14 04:53:48 +00:00
|
|
|
export let company: Ref<Organization> | undefined
|
|
|
|
export let preserveCompany: boolean = false
|
2021-08-07 17:03:06 +00:00
|
|
|
|
2023-03-17 15:57:36 +00:00
|
|
|
let vacancyData: Data<VacancyClass> = {
|
|
|
|
archived: false,
|
|
|
|
description: '',
|
|
|
|
members: [],
|
|
|
|
name: '',
|
|
|
|
number: 0,
|
|
|
|
private: false,
|
|
|
|
attachments: 0,
|
|
|
|
comments: 0,
|
|
|
|
company: '' as Ref<Organization>,
|
|
|
|
fullDescription: '',
|
|
|
|
location: ''
|
|
|
|
}
|
2022-02-09 09:08:09 +00:00
|
|
|
export function canClose (): boolean {
|
2021-12-21 09:42:18 +00:00
|
|
|
return name === '' && templateId !== undefined
|
2021-11-04 11:17:59 +00:00
|
|
|
}
|
|
|
|
|
2021-08-07 17:03:06 +00:00
|
|
|
const client = getClient()
|
2023-03-21 16:08:45 +00:00
|
|
|
const hierarchy = client.getHierarchy()
|
2022-11-09 11:24:02 +00:00
|
|
|
const templateQ = createQuery()
|
2023-03-21 16:08:45 +00:00
|
|
|
fillDefaults(hierarchy, vacancyData, recruit.class.Vacancy)
|
2023-03-27 06:12:01 +00:00
|
|
|
$: templateId &&
|
|
|
|
templateQ.query(task.class.KanbanTemplate, { _id: templateId }, (result) => {
|
|
|
|
const { _class, _id, description, ...templateData } = result[0]
|
|
|
|
vacancyData = { ...(templateData as unknown as Data<VacancyClass>), fullDescription: description }
|
|
|
|
if (appliedTemplateId !== templateId) {
|
|
|
|
fullDescription = description ?? ''
|
|
|
|
appliedTemplateId = templateId
|
|
|
|
}
|
|
|
|
fillDefaults(hierarchy, vacancyData, recruit.class.Vacancy)
|
|
|
|
})
|
2022-11-09 11:24:02 +00:00
|
|
|
|
|
|
|
const issueTemplatesQ = createQuery()
|
|
|
|
$: issueTemplatesQ.query(tracker.class.IssueTemplate, { 'relations._id': templateId }, async (result) => {
|
|
|
|
issueTemplates = result
|
|
|
|
})
|
2021-08-07 17:03:06 +00:00
|
|
|
|
2023-02-17 09:26:44 +00:00
|
|
|
async function saveIssue (
|
|
|
|
id: Ref<VacancyClass>,
|
2023-03-17 06:17:53 +00:00
|
|
|
space: Ref<Project>,
|
2023-02-17 09:26:44 +00:00
|
|
|
template: IssueTemplateData,
|
|
|
|
parent: Ref<Issue> = tracker.ids.NoParent
|
|
|
|
): Promise<Ref<Issue>> {
|
|
|
|
const lastOne = await client.findOne<Issue>(
|
|
|
|
tracker.class.Issue,
|
|
|
|
{ space },
|
|
|
|
{ sort: { rank: SortingOrder.Descending } }
|
|
|
|
)
|
|
|
|
const incResult = await client.updateDoc(
|
2023-03-17 06:17:53 +00:00
|
|
|
tracker.class.Project,
|
2023-02-17 09:26:44 +00:00
|
|
|
core.space.Space,
|
|
|
|
space,
|
|
|
|
{
|
|
|
|
$inc: { sequence: 1 }
|
|
|
|
},
|
|
|
|
true
|
|
|
|
)
|
2023-03-17 06:17:53 +00:00
|
|
|
const project = await client.findOne(tracker.class.Project, { _id: space })
|
2023-02-17 09:26:44 +00:00
|
|
|
const rank = calcRank(lastOne, undefined)
|
|
|
|
const resId = await client.addCollection(tracker.class.Issue, space, parent, tracker.class.Issue, 'subIssues', {
|
|
|
|
title: template.title + ` (${name})`,
|
|
|
|
description: template.description,
|
|
|
|
assignee: template.assignee,
|
2023-03-17 06:17:53 +00:00
|
|
|
component: template.component,
|
2023-02-17 09:26:44 +00:00
|
|
|
sprint: template.sprint,
|
|
|
|
number: (incResult as any).object.sequence,
|
2023-03-17 06:17:53 +00:00
|
|
|
status: project?.defaultIssueStatus as Ref<IssueStatus>,
|
2023-02-17 09:26:44 +00:00
|
|
|
priority: template.priority,
|
|
|
|
rank,
|
|
|
|
comments: 0,
|
|
|
|
subIssues: 0,
|
|
|
|
dueDate: null,
|
|
|
|
parents: [],
|
|
|
|
reportedTime: 0,
|
|
|
|
estimation: template.estimation,
|
|
|
|
reports: 0,
|
|
|
|
relations: [{ _id: id, _class: recruit.class.Vacancy }],
|
2023-03-21 04:43:51 +00:00
|
|
|
childInfo: [],
|
|
|
|
createOn: Date.now()
|
2023-02-17 09:26:44 +00:00
|
|
|
})
|
|
|
|
if ((template.labels?.length ?? 0) > 0) {
|
|
|
|
const tagElements = await client.findAll(tags.class.TagElement, { _id: { $in: template.labels } })
|
|
|
|
for (const label of tagElements) {
|
|
|
|
await client.addCollection(tags.class.TagReference, space, resId, tracker.class.Issue, 'labels', {
|
|
|
|
title: label.title,
|
|
|
|
color: label.color,
|
|
|
|
tag: label._id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return resId
|
|
|
|
}
|
|
|
|
|
2022-02-09 09:08:09 +00:00
|
|
|
async function createVacancy () {
|
2022-04-29 05:27:17 +00:00
|
|
|
if (
|
|
|
|
templateId !== undefined &&
|
|
|
|
(await client.findOne(task.class.KanbanTemplate, { _id: templateId })) === undefined
|
|
|
|
) {
|
2021-12-14 09:24:14 +00:00
|
|
|
throw Error(`Failed to find target kanban template: ${templateId}`)
|
|
|
|
}
|
|
|
|
|
2023-03-15 14:06:03 +00:00
|
|
|
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)
|
|
|
|
|
2022-10-05 06:41:02 +00:00
|
|
|
const id = await client.createDoc(
|
|
|
|
recruit.class.Vacancy,
|
|
|
|
core.space.Space,
|
|
|
|
{
|
2023-03-17 15:57:36 +00:00
|
|
|
...vacancyData,
|
2022-10-05 06:41:02 +00:00
|
|
|
name,
|
2022-11-09 11:24:02 +00:00
|
|
|
description: template?.shortDescription ?? '',
|
2023-01-31 05:48:40 +00:00
|
|
|
fullDescription,
|
2022-10-05 06:41:02 +00:00
|
|
|
private: false,
|
|
|
|
archived: false,
|
2023-03-15 14:06:03 +00:00
|
|
|
number: (incResult as any).object.sequence,
|
2022-10-05 06:41:02 +00:00
|
|
|
company,
|
|
|
|
members: [getCurrentAccount()._id]
|
|
|
|
},
|
|
|
|
objectId
|
|
|
|
)
|
2021-12-14 09:24:14 +00:00
|
|
|
|
2023-02-10 01:45:22 +00:00
|
|
|
if (issueTemplates.length > 0) {
|
|
|
|
for (const issueTemplate of issueTemplates) {
|
2023-02-17 09:26:44 +00:00
|
|
|
const issue = await saveIssue(id, issueTemplate.space, issueTemplate)
|
|
|
|
for (const sub of issueTemplate.children) {
|
|
|
|
await saveIssue(id, issueTemplate.space, sub, issue)
|
2022-11-09 11:24:02 +00:00
|
|
|
}
|
2023-02-10 01:45:22 +00:00
|
|
|
}
|
2022-11-09 11:24:02 +00:00
|
|
|
}
|
|
|
|
|
2021-12-14 09:24:14 +00:00
|
|
|
await createKanban(client, id, templateId)
|
2022-10-05 06:41:02 +00:00
|
|
|
|
|
|
|
await descriptionBox.createAttachments()
|
|
|
|
objectId = generateId()
|
|
|
|
|
2022-05-16 11:41:22 +00:00
|
|
|
dispatch('close', id)
|
2021-08-07 17:03:06 +00:00
|
|
|
}
|
2022-05-16 11:41:22 +00:00
|
|
|
const manager = createFocusManager()
|
2022-10-05 06:41:02 +00:00
|
|
|
|
|
|
|
let descriptionBox: AttachmentStyledBox
|
2023-03-17 15:57:36 +00:00
|
|
|
|
|
|
|
function handleTemplateChange (evt: CustomEvent<Ref<KanbanTemplate>>): void {
|
|
|
|
if (templateId == null) {
|
|
|
|
templateId = evt.detail
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// Template is already specified, ask to replace.
|
|
|
|
showPopup(
|
|
|
|
MessageBox,
|
|
|
|
{
|
|
|
|
label: recruit.string.TemplateReplace,
|
|
|
|
message: recruit.string.TemplateReplaceConfirm
|
|
|
|
},
|
|
|
|
'top',
|
|
|
|
(result?: boolean) => {
|
|
|
|
if (result === true) {
|
|
|
|
templateId = evt.detail ?? undefined
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
2021-08-07 17:03:06 +00:00
|
|
|
</script>
|
|
|
|
|
2022-05-16 11:41:22 +00:00
|
|
|
<FocusHandler {manager} />
|
2022-04-29 05:27:17 +00:00
|
|
|
<Card
|
|
|
|
label={recruit.string.CreateVacancy}
|
2021-11-02 08:45:08 +00:00
|
|
|
okAction={createVacancy}
|
2022-02-09 09:08:09 +00:00
|
|
|
canSave={!!name}
|
2022-04-29 05:27:17 +00:00
|
|
|
on:close={() => {
|
|
|
|
dispatch('close')
|
|
|
|
}}
|
2023-04-07 16:59:18 +00:00
|
|
|
on:changeContent
|
2021-11-02 08:45:08 +00:00
|
|
|
>
|
2022-04-13 04:25:59 +00:00
|
|
|
<div class="flex-row-center clear-mins">
|
|
|
|
<div class="mr-3">
|
2022-05-16 11:41:22 +00:00
|
|
|
<Button focusIndex={1} icon={Vacancy} size={'medium'} kind={'link-bordered'} disabled />
|
2022-04-13 04:25:59 +00:00
|
|
|
</div>
|
2022-10-04 15:49:11 +00:00
|
|
|
<div class="clear-mins flex-grow">
|
|
|
|
<EditBox
|
|
|
|
focusIndex={2}
|
|
|
|
bind:value={name}
|
|
|
|
placeholder={recruit.string.VacancyPlaceholder}
|
|
|
|
kind={'large-style'}
|
|
|
|
focus
|
|
|
|
/>
|
|
|
|
</div>
|
2022-04-13 04:25:59 +00:00
|
|
|
</div>
|
2023-03-21 04:43:51 +00:00
|
|
|
{#key appliedTemplateId}
|
2022-12-07 05:02:03 +00:00
|
|
|
<AttachmentStyledBox
|
|
|
|
bind:this={descriptionBox}
|
|
|
|
{objectId}
|
|
|
|
_class={recruit.class.Vacancy}
|
|
|
|
space={objectId}
|
|
|
|
alwaysEdit
|
|
|
|
showButtons={false}
|
|
|
|
maxHeight={'card'}
|
2023-01-31 05:48:40 +00:00
|
|
|
bind:content={fullDescription}
|
2022-12-07 05:02:03 +00:00
|
|
|
placeholder={recruit.string.FullDescription}
|
|
|
|
emphasized
|
|
|
|
/>
|
|
|
|
{/key}
|
|
|
|
|
2022-10-05 06:41:02 +00:00
|
|
|
<svelte:fragment slot="header">
|
2022-05-16 11:41:22 +00:00
|
|
|
<UserBox
|
|
|
|
focusIndex={3}
|
|
|
|
_class={contact.class.Organization}
|
|
|
|
label={recruit.string.Company}
|
|
|
|
placeholder={recruit.string.Company}
|
2022-09-14 04:53:48 +00:00
|
|
|
justify={'left'}
|
2022-05-16 11:41:22 +00:00
|
|
|
bind:value={company}
|
|
|
|
allowDeselect
|
|
|
|
titleDeselect={recruit.string.UnAssignCompany}
|
2023-04-25 02:59:07 +00:00
|
|
|
kind={'secondary'}
|
|
|
|
size={'large'}
|
2022-05-16 11:41:22 +00:00
|
|
|
icon={Company}
|
2022-09-14 04:53:48 +00:00
|
|
|
readonly={preserveCompany}
|
|
|
|
showNavigate={false}
|
2022-05-16 11:41:22 +00:00
|
|
|
create={{ component: contact.component.CreateOrganization, label: contact.string.CreateOrganization }}
|
|
|
|
/>
|
2022-04-29 05:27:17 +00:00
|
|
|
<Component
|
|
|
|
is={task.component.KanbanTemplateSelector}
|
|
|
|
props={{
|
|
|
|
folders: [recruit.space.VacancyTemplates],
|
2022-05-16 11:41:22 +00:00
|
|
|
template: templateId,
|
2023-04-25 02:59:07 +00:00
|
|
|
focusIndex: 4,
|
|
|
|
kind: 'secondary',
|
|
|
|
size: 'large'
|
2022-04-29 05:27:17 +00:00
|
|
|
}}
|
2023-03-17 15:57:36 +00:00
|
|
|
on:change={handleTemplateChange}
|
|
|
|
/>
|
|
|
|
</svelte:fragment>
|
|
|
|
|
|
|
|
<svelte:fragment slot="pool">
|
|
|
|
<InlineAttributeBar
|
|
|
|
_class={recruit.class.Vacancy}
|
|
|
|
object={vacancyData}
|
|
|
|
toClass={core.class.Space}
|
|
|
|
ignoreKeys={['fullDescription', 'company']}
|
|
|
|
extraProps={{ showNavigate: false }}
|
2022-04-09 03:46:40 +00:00
|
|
|
/>
|
2022-04-13 04:25:59 +00:00
|
|
|
</svelte:fragment>
|
2022-10-05 06:41:02 +00:00
|
|
|
<svelte:fragment slot="footer">
|
|
|
|
<Button
|
|
|
|
icon={IconAttachment}
|
2023-04-25 02:59:07 +00:00
|
|
|
size={'large'}
|
2022-10-05 06:41:02 +00:00
|
|
|
on:click={() => {
|
|
|
|
descriptionBox.attach()
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</svelte:fragment>
|
2022-04-13 04:25:59 +00:00
|
|
|
</Card>
|