// // Copyright © 2022 Hardcore Engineering Inc. // // 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. // import type { Employee, Organization } from '@hcengineering/contact' import { IndexKind, Lookup, Ref, SortingOrder, Timestamp } from '@hcengineering/core' import { Builder, Collection, Hidden, Index, Mixin, Model, Prop, ReadOnly, TypeBoolean, TypeDate, TypeMarkup, TypeRef, TypeString, UX } from '@hcengineering/model' import attachment from '@hcengineering/model-attachment' import calendar from '@hcengineering/model-calendar' import chunter from '@hcengineering/model-chunter' import contact, { TOrganization, TPerson } from '@hcengineering/model-contact' import core, { TAttachedDoc, TSpace } from '@hcengineering/model-core' import { generateClassNotificationTypes } from '@hcengineering/model-notification' import presentation from '@hcengineering/model-presentation' import tags from '@hcengineering/model-tags' import task, { DOMAIN_TASK, TSpaceWithStates, TTask, actionTemplates } from '@hcengineering/model-task' import tracker from '@hcengineering/model-tracker' import view, { createAction, showColorsViewOption, actionTemplates as viewTemplates } from '@hcengineering/model-view' import workbench, { Application, createNavigateAction } from '@hcengineering/model-workbench' import notification from '@hcengineering/notification' import { IntlString, getEmbeddedLabel } from '@hcengineering/platform' import { Applicant, ApplicantMatch, Candidate, Candidates, Vacancy, VacancyList, recruitId } from '@hcengineering/recruit' import setting from '@hcengineering/setting' import { KeyBinding, ViewOptionsModel } from '@hcengineering/view' import recruit from './plugin' import { createReviewModel, reviewTableConfig, reviewTableOptions } from './review' import { TOpinion, TReview } from './review-model' export { recruitId } from '@hcengineering/recruit' export { recruitOperation } from './migration' export { default } from './plugin' @Model(recruit.class.Vacancy, task.class.SpaceWithStates) @UX(recruit.string.Vacancy, recruit.icon.Vacancy, 'VCN', 'name') export class TVacancy extends TSpaceWithStates implements Vacancy { @Prop(TypeMarkup(), recruit.string.FullDescription) @Index(IndexKind.FullText) fullDescription?: string @Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files }) attachments?: number @Prop(TypeDate(), recruit.string.Due, recruit.icon.Calendar) dueTo?: Timestamp @Prop(TypeString(), recruit.string.Location, recruit.icon.Location) @Index(IndexKind.FullText) location?: string @Prop(TypeRef(contact.class.Organization), recruit.string.Company, { icon: contact.icon.Company }) company?: Ref @Prop(Collection(chunter.class.Comment), chunter.string.Comments) comments?: number relations!: number @Prop(TypeString(), recruit.string.Vacancy) @Index(IndexKind.FullText) @Hidden() number!: number } @Model(recruit.class.Candidates, core.class.Space) @UX(recruit.string.TalentPools, recruit.icon.RecruitApplication) export class TCandidates extends TSpace implements Candidates {} @Mixin(recruit.mixin.Candidate, contact.class.Person) @UX(recruit.string.Talent, recruit.icon.RecruitApplication, 'TLNT', 'name') export class TCandidate extends TPerson implements Candidate { @Prop(TypeString(), recruit.string.Title) @Index(IndexKind.FullText) title?: string @Prop(Collection(recruit.class.Applicant), recruit.string.Applications, { shortLabel: recruit.string.ApplicationsShort }) applications?: number @Prop(TypeBoolean(), recruit.string.Onsite) onsite?: boolean @Prop(TypeBoolean(), recruit.string.Remote) remote?: boolean @Prop(TypeString(), recruit.string.Source) @Index(IndexKind.FullText) source?: string @Prop(Collection(tags.class.TagReference, recruit.string.SkillLabel), recruit.string.SkillsLabel, { icon: recruit.icon.Skills, schema: '3' }) skills?: number @Prop(Collection(recruit.class.Review, recruit.string.Review), recruit.string.Reviews) reviews?: number @Prop( Collection(recruit.class.ApplicantMatch, getEmbeddedLabel('Vacancy match')), getEmbeddedLabel('Vacancy Matches') ) vacancyMatch?: number } @Mixin(recruit.mixin.VacancyList, contact.class.Organization) @UX(recruit.string.VacancyList, recruit.icon.RecruitApplication, 'CM', 'name') export class TVacancyList extends TOrganization implements VacancyList { @Prop(Collection(recruit.class.Vacancy), recruit.string.Vacancies) vacancies!: number } @Model(recruit.class.Applicant, task.class.Task) @UX(recruit.string.Application, recruit.icon.Application, 'APP', 'number') export class TApplicant extends TTask implements Applicant { // We need to declare, to provide property with label @Prop(TypeRef(recruit.mixin.Candidate), recruit.string.Talent) @Index(IndexKind.Indexed) @ReadOnly() declare attachedTo: Ref // We need to declare, to provide property with label @Prop(TypeRef(recruit.class.Vacancy), recruit.string.Vacancy) @Index(IndexKind.Indexed) declare space: Ref @Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files }) attachments?: number @Prop(Collection(chunter.class.Comment), chunter.string.Comments) comments?: number @Prop(TypeRef(contact.class.Employee), recruit.string.AssignedRecruiter) declare assignee: Ref | null } @Model(recruit.class.ApplicantMatch, core.class.AttachedDoc, DOMAIN_TASK) @UX(recruit.string.Application, recruit.icon.Application, 'APP', 'number') export class TApplicantMatch extends TAttachedDoc implements ApplicantMatch { // We need to declare, to provide property with label @Prop(TypeRef(recruit.mixin.Candidate), recruit.string.Talent) @Index(IndexKind.Indexed) declare attachedTo: Ref @Prop(TypeBoolean(), getEmbeddedLabel('Complete')) @ReadOnly() complete!: boolean @Prop(TypeString(), getEmbeddedLabel('Vacancy')) @ReadOnly() vacancy!: string @Prop(TypeString(), getEmbeddedLabel('Summary')) @ReadOnly() summary!: string @Prop(TypeMarkup(), getEmbeddedLabel('Response')) @ReadOnly() response!: string } export function createModel (builder: Builder): void { builder.createModel(TVacancy, TCandidates, TCandidate, TApplicant, TReview, TOpinion, TVacancyList, TApplicantMatch) builder.mixin(recruit.class.Vacancy, core.class.Class, workbench.mixin.SpaceView, { view: { class: recruit.class.Applicant, createItemDialog: recruit.component.CreateApplication, createItemLabel: recruit.string.ApplicationCreateLabel } }) builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.CollectionEditor, { editor: recruit.component.Applications }) builder.mixin(recruit.class.Vacancy, core.class.Class, view.mixin.CollectionEditor, { editor: recruit.component.VacancyList }) builder.mixin(recruit.class.Vacancy, core.class.Class, notification.mixin.ClassCollaborators, { fields: ['createdBy'] }) builder.mixin(recruit.class.Applicant, core.class.Class, notification.mixin.ClassCollaborators, { fields: ['createdBy'] }) builder.mixin(recruit.mixin.Candidate, core.class.Mixin, view.mixin.ObjectFactory, { component: recruit.component.CreateCandidate }) builder.mixin(recruit.class.Applicant, core.class.Class, setting.mixin.Editable, { value: true }) builder.mixin(recruit.class.Vacancy, core.class.Class, setting.mixin.Editable, { value: true }) builder.mixin(recruit.mixin.VacancyList, core.class.Class, setting.mixin.Editable, { value: false }) const vacanciesId = 'vacancies' const talentsId = 'talents' const skillsId = 'skills' const candidatesId = 'candidates' const archiveId = 'archive' const myApplicationsId = 'my-applications' const organizationsId = 'organizations' builder.createDoc( workbench.class.Application, core.space.Model, { label: recruit.string.RecruitApplication, icon: recruit.icon.RecruitApplication, locationResolver: recruit.resolver.Location, alias: recruitId, hidden: false, navigatorModel: { spaces: [], specials: [ { id: vacanciesId, component: recruit.component.Vacancies, icon: recruit.icon.Vacancy, label: recruit.string.Vacancies, createItemLabel: recruit.string.VacancyCreateLabel, position: 'vacancy', componentProps: { archived: false } }, { id: organizationsId, component: recruit.component.Organizations, icon: contact.icon.Company, label: recruit.string.Organizations, position: 'vacancy' }, { id: candidatesId, component: workbench.component.SpecialView, icon: recruit.icon.Application, label: recruit.string.Applications, componentProps: { _class: recruit.class.Applicant, icon: recruit.icon.Application, label: recruit.string.Applications, createLabel: recruit.string.ApplicationCreateLabel, createComponent: recruit.component.CreateApplication, descriptors: [ view.viewlet.Table, view.viewlet.List, task.viewlet.Kanban, recruit.viewlet.ApplicantDashboard ] }, position: 'vacancy' }, { id: talentsId, component: workbench.component.SpecialView, icon: recruit.icon.Talents, label: recruit.string.Talents, componentProps: { _class: recruit.mixin.Candidate, icon: contact.icon.Person, label: recruit.string.Talents, createLabel: recruit.string.TalentCreateLabel, createComponent: recruit.component.CreateCandidate, createComponentProps: { shouldSaveDraft: false } }, position: 'vacancy' }, { id: archiveId, component: recruit.component.Vacancies, icon: view.icon.Archive, label: workbench.string.Archive, position: 'bottom', visibleIf: workbench.function.HasArchiveSpaces, spaceClass: recruit.class.Vacancy, componentProps: { archived: true } }, { id: skillsId, component: recruit.component.SkillsView, icon: recruit.icon.Skills, label: recruit.string.SkillsLabel, createItemLabel: recruit.string.SkillCreateLabel, position: 'bottom' }, { id: myApplicationsId, label: recruit.string.MyApplications, icon: recruit.icon.AssignedToMe, component: task.component.AssignedTasks, position: 'event', componentProps: { labelTasks: recruit.string.Applications, _class: recruit.class.Applicant } }, { id: 'reviews', component: calendar.component.Events, componentProps: { viewLabel: recruit.string.Reviews, viewIcon: recruit.icon.Review, _class: recruit.class.Review, options: reviewTableOptions, config: reviewTableConfig, createLabel: recruit.string.ReviewCreateLabel, createComponent: recruit.component.CreateReview }, icon: recruit.icon.Reviews, label: recruit.string.Reviews, position: 'event' } ] }, navHeaderComponent: recruit.component.NewCandidateHeader }, recruit.app.Recruit ) builder.createDoc( view.class.Viewlet, core.space.Model, { attachTo: recruit.mixin.Candidate, descriptor: view.viewlet.Table, config: [ '', 'title', 'city', 'applications', 'attachments', { key: '', presenter: tracker.component.RelatedIssueSelector, label: tracker.string.Relations }, 'comments', { // key: '$lookup.skills', // Required, since presenter require list of tag references or '' and TagsPopupPresenter key: '', presenter: tags.component.TagsPresenter, label: recruit.string.SkillsLabel, sortingKey: 'skills', props: { _class: recruit.mixin.Candidate, key: 'skills', icon: recruit.icon.Skills } }, 'modifiedOn', { key: '$lookup.channels', label: contact.string.ContactInfo, sortingKey: ['$lookup.channels.lastMessage', 'channels'] } ], hiddenKeys: ['name'], options: { lookup: { _id: { related: [tracker.class.Issue, 'relations._id'] } } } }, recruit.viewlet.TableCandidate ) builder.createDoc( view.class.Viewlet, core.space.Model, { attachTo: recruit.class.Applicant, descriptor: view.viewlet.Table, config: ['', '$lookup.attachedTo', 'state', 'doneState', 'modifiedOn'], variant: 'short' }, recruit.viewlet.VacancyApplicationsShort ) builder.createDoc( view.class.Viewlet, core.space.Model, { attachTo: recruit.class.Applicant, descriptor: view.viewlet.Table, config: ['', '$lookup.space.name', '$lookup.space.$lookup.company', 'state', 'comments', 'doneState'], variant: 'embedded' }, recruit.viewlet.VacancyApplicationsEmbeddeed ) builder.createDoc( view.class.Viewlet, core.space.Model, { attachTo: recruit.class.Vacancy, descriptor: view.viewlet.Table, config: [ '', { key: '@applications', label: recruit.string.Applications }, 'comments', '$lookup.company', '$lookup.company.$lookup.channels', 'location', 'description', { key: '@applications.modifiedOn', label: core.string.ModifiedDate } ], hiddenKeys: ['name', 'space', 'modifiedOn'] }, recruit.viewlet.TableVacancy ) builder.createDoc( view.class.Viewlet, core.space.Model, { attachTo: recruit.mixin.VacancyList, descriptor: view.viewlet.Table, config: [ { key: '', label: recruit.string.Organizations }, { key: '@vacancies', label: recruit.string.Vacancies }, { key: '@applications', label: recruit.string.Applications }, 'comments', '$lookup.channels', { key: '@applications.modifiedOn', label: core.string.ModifiedDate } ], hiddenKeys: ['name', 'space', 'modifiedOn'] }, recruit.viewlet.TableVacancyList ) builder.createDoc( view.class.Viewlet, core.space.Model, { attachTo: recruit.class.Applicant, descriptor: task.viewlet.StatusTable, config: [ '', { key: '$lookup.attachedTo', presenter: contact.component.PersonPresenter, sortingKey: '$lookup.attachedTo.name', label: recruit.string.Talent, props: { _class: recruit.mixin.Candidate } }, 'assignee', { key: '', presenter: tracker.component.RelatedIssueSelector, label: tracker.string.Issues }, 'state', 'doneState', 'attachments', 'comments', 'modifiedOn', { key: '$lookup.attachedTo.$lookup.channels', label: contact.string.ContactInfo, sortingKey: ['$lookup.attachedTo.$lookup.channels.lastMessage', '$lookup.attachedTo.channels'] } ], hiddenKeys: ['name', 'attachedTo'], options: { lookup: { _id: { related: [tracker.class.Issue, 'relations._id'] } } } }, recruit.viewlet.TableApplicant ) builder.createDoc( view.class.Viewlet, core.space.Model, { attachTo: recruit.class.Applicant, descriptor: view.viewlet.Table, config: [ '', { key: '$lookup.attachedTo', presenter: contact.component.PersonPresenter, label: recruit.string.Talent, sortingKey: '$lookup.attachedTo.name', props: { _class: recruit.mixin.Candidate } }, 'assignee', { key: '', presenter: tracker.component.RelatedIssueSelector, label: tracker.string.Issues }, 'state', 'attachments', 'comments', 'modifiedOn', '$lookup.space.company', { key: '$lookup.attachedTo.$lookup.channels', label: contact.string.ContactInfo, sortingKey: ['$lookup.attachedTo.$lookup.channels.lastMessage', '$lookup.attachedTo.channels'] } ], options: { lookup: { _id: { related: [tracker.class.Issue, 'relations._id'] }, space: recruit.class.Vacancy } }, hiddenKeys: ['name', 'attachedTo'], baseQuery: { doneState: null, '$lookup.space.archived': false } }, recruit.viewlet.ApplicantTable ) builder.createDoc( view.class.Viewlet, core.space.Model, { attachTo: recruit.class.ApplicantMatch, descriptor: view.viewlet.Table, config: ['', 'response', 'attachedTo', 'space', 'modifiedOn'], hiddenKeys: [], options: { lookup: { space: recruit.class.Vacancy } }, baseQuery: { doneState: null, '$lookup.space.archived': false } }, recruit.viewlet.TableApplicantMatch ) const applicantKanbanLookup: Lookup = { attachedTo: [ recruit.mixin.Candidate, { _id: { channels: contact.class.Channel }, space: recruit.class.Vacancy } ], _id: { related: [tracker.class.Issue, 'relations._id'] } } const applicantViewOptions = (colors: boolean): ViewOptionsModel => { const model: ViewOptionsModel = { groupBy: ['state', 'assignee', 'space'], orderBy: [ ['state', SortingOrder.Ascending], ['modifiedOn', SortingOrder.Descending], ['createdOn', SortingOrder.Descending], ['dueDate', SortingOrder.Ascending], ['rank', SortingOrder.Ascending] ], other: [ { key: 'shouldShowAll', type: 'toggle', defaultValue: false, actionTarget: 'category', action: view.function.ShowEmptyGroups, label: view.string.ShowEmptyGroups } ] } if (colors) { model.other.push(showColorsViewOption) } return model } builder.createDoc( view.class.Viewlet, core.space.Model, { attachTo: recruit.class.Applicant, descriptor: view.viewlet.List, config: [ { key: '', props: { listProps: { fixed: 'left', key: 'app' } } }, { key: '$lookup.attachedTo', presenter: contact.component.PersonPresenter, label: recruit.string.Talent, sortingKey: '$lookup.attachedTo.name', props: { _class: recruit.mixin.Candidate, listProps: { fixed: 'left', key: 'talent' }, inline: true } }, { key: 'state', props: { listProps: { fixed: 'left', key: 'state' }, inline: true, showLabel: false } }, { key: '$lookup.space.company', props: { listProps: { fixed: 'left', key: 'company' }, inline: true, maxWidth: '10rem' } }, { key: '', presenter: tracker.component.RelatedIssueSelector, label: tracker.string.Issues, props: { listProps: { fixed: 'left', key: 'issues' } } }, { key: 'attachments', props: { listProps: { fixed: 'left', key: 'attachments' } } }, { key: 'comments', props: { listProps: { fixed: 'left' }, key: 'comments' } }, { key: '', presenter: view.component.GrowPresenter, props: { type: 'grow' } }, { key: '', presenter: view.component.DividerPresenter, props: { type: 'divider' } }, { key: '$lookup.attachedTo.$lookup.channels', label: contact.string.ContactInfo, sortingKey: ['$lookup.attachedTo.$lookup.channels.lastMessage', '$lookup.attachedTo.channels'], props: { listProps: { fixed: 'left', key: 'channels' } } }, { key: '', presenter: view.component.DividerPresenter, props: { type: 'divider' } }, { key: 'modifiedOn', props: { listProps: { key: 'modified', fixed: 'left' } } }, { key: 'assignee', props: { listProps: { key: 'assignee', fixed: 'right' }, shouldShowLabel: false } } ], options: { lookup: { _id: { related: [tracker.class.Issue, 'relations._id'] }, space: recruit.class.Vacancy } }, hiddenKeys: ['name', 'attachedTo'], baseQuery: { doneState: null, '$lookup.space.archived': false }, viewOptions: applicantViewOptions(true) }, recruit.viewlet.ListApplicant ) builder.createDoc( view.class.Viewlet, core.space.Model, { attachTo: recruit.class.Applicant, descriptor: task.viewlet.Kanban, // eslint-disable-next-line @typescript-eslint/consistent-type-assertions baseQuery: { doneState: null, '$lookup.space.archived': false }, viewOptions: { ...applicantViewOptions(false), groupDepth: 1 }, options: { lookup: applicantKanbanLookup }, config: [] }, recruit.viewlet.ApplicantKanban ) builder.createDoc( view.class.Viewlet, core.space.Model, { attachTo: recruit.class.Applicant, descriptor: task.viewlet.Dashboard, options: {}, config: [] }, recruit.viewlet.ApplicantDashboard ) builder.mixin(recruit.class.Applicant, core.class.Class, task.mixin.KanbanCard, { card: recruit.component.KanbanCard }) builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.PreviewPresenter, { presenter: recruit.component.KanbanCard }) builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.ObjectEditor, { editor: recruit.component.EditApplication }) builder.mixin(recruit.class.Vacancy, core.class.Class, view.mixin.ObjectEditor, { editor: recruit.component.EditVacancy }) builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.ObjectPresenter, { presenter: recruit.component.ApplicationPresenter }) builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.CollectionPresenter, { presenter: recruit.component.ApplicationsPresenter }) builder.mixin(recruit.class.ApplicantMatch, core.class.Class, view.mixin.ObjectPresenter, { presenter: recruit.component.ApplicationMatchPresenter }) builder.mixin(recruit.class.ApplicantMatch, core.class.Class, view.mixin.CollectionPresenter, { presenter: recruit.component.ApplicationMatchPresenter }) builder.mixin(recruit.class.Vacancy, core.class.Class, view.mixin.ObjectPresenter, { presenter: recruit.component.VacancyPresenter }) builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.ObjectValidator, { validator: recruit.validator.ApplicantValidator }) builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.ObjectTitle, { titleProvider: recruit.function.AppTitleProvider }) builder.mixin(recruit.class.Review, core.class.Class, view.mixin.ObjectTitle, { titleProvider: recruit.function.RevTitleProvider }) builder.mixin(recruit.class.Vacancy, core.class.Class, view.mixin.ObjectTitle, { titleProvider: recruit.function.VacTitleProvider }) builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.LinkProvider, { encode: recruit.function.GetObjectLinkFragment }) builder.mixin(recruit.class.Opinion, core.class.Class, view.mixin.LinkProvider, { encode: recruit.function.GetObjectLinkFragment }) builder.mixin(recruit.class.Review, core.class.Class, view.mixin.LinkProvider, { encode: recruit.function.GetObjectLinkFragment }) builder.mixin(recruit.class.Vacancy, core.class.Class, view.mixin.LinkProvider, { encode: recruit.function.GetObjectLinkFragment }) builder.mixin(recruit.mixin.Candidate, core.class.Class, view.mixin.LinkProvider, { encode: recruit.function.GetIdObjectLinkFragment }) builder.mixin(recruit.mixin.VacancyList, core.class.Class, view.mixin.LinkProvider, { encode: recruit.function.GetIdObjectLinkFragment }) builder.createDoc( view.class.ActionCategory, core.space.Model, { label: recruit.string.RecruitApplication, visible: true }, recruit.category.Recruit ) createAction(builder, { action: view.actionImpl.ShowPopup, actionProps: { component: recruit.component.CreateApplication, element: 'top', props: { preserveCandidate: true }, fillProps: { _id: 'candidate' } }, label: recruit.string.CreateAnApplication, icon: recruit.icon.Create, input: 'focus', category: recruit.category.Recruit, target: contact.class.Person, context: { mode: ['context', 'browser'], group: 'associate' }, override: [recruit.action.CreateGlobalApplication] }) createAction(builder, { action: view.actionImpl.ShowPopup, actionProps: { component: recruit.component.CreateCandidate, element: 'top' }, label: recruit.string.CreateTalent, icon: recruit.icon.Create, keyBinding: ['keyC'], input: 'none', category: recruit.category.Recruit, target: core.class.Doc, context: { mode: ['workbench', 'browser'], application: recruit.app.Recruit, group: 'create' } }) createAction(builder, { action: view.actionImpl.ShowPopup, actionProps: { component: recruit.component.CreateVacancy, element: 'top' }, label: recruit.string.CreateVacancy, icon: recruit.icon.Create, keyBinding: [], input: 'none', category: recruit.category.Recruit, target: core.class.Doc, context: { mode: ['workbench', 'browser'], application: recruit.app.Recruit, group: 'create' } }) createAction( builder, { action: view.actionImpl.ShowPopup, actionProps: { component: recruit.component.CreateApplication, element: 'top' }, label: recruit.string.CreateApplication, icon: recruit.icon.Create, keyBinding: [], input: 'none', category: recruit.category.Recruit, target: core.class.Doc, context: { mode: ['workbench', 'browser'], application: recruit.app.Recruit, group: 'create' } }, recruit.action.CreateGlobalApplication ) builder.createDoc( presentation.class.ObjectSearchCategory, core.space.Model, { icon: recruit.icon.Application, label: recruit.string.SearchApplication, query: recruit.completion.ApplicationQuery }, recruit.completion.ApplicationCategory ) builder.createDoc( presentation.class.ObjectSearchCategory, core.space.Model, { icon: recruit.icon.Vacancy, label: recruit.string.SearchVacancy, query: recruit.completion.VacancyQuery }, recruit.completion.VacancyCategory ) createAction(builder, { ...actionTemplates.archiveSpace, target: recruit.class.Vacancy }) createAction(builder, { ...actionTemplates.unarchiveSpace, target: recruit.class.Vacancy }) createAction(builder, { label: recruit.string.EditVacancy, icon: view.icon.Edit, action: view.actionImpl.ShowPanel, actionProps: { component: recruit.component.EditVacancy, element: 'content' }, input: 'focus', category: recruit.category.Recruit, override: [view.action.Open], keyBinding: ['keyE'], target: recruit.class.Vacancy, context: { mode: ['context', 'browser'], group: 'create' } }) builder.mixin(recruit.class.Vacancy, core.class.Class, view.mixin.IgnoreActions, { actions: [view.action.Delete] }) builder.mixin(recruit.class.Vacancy, core.class.Class, view.mixin.ObjectPanel, { component: recruit.component.EditVacancy }) builder.mixin(recruit.mixin.Candidate, core.class.Class, view.mixin.ClassFilters, { filters: ['_class'] }) builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.ClassFilters, { filters: ['attachedTo'] }) builder.mixin(recruit.class.Vacancy, core.class.Class, view.mixin.ClassFilters, { filters: [] }) builder.mixin(recruit.mixin.VacancyList, core.class.Class, view.mixin.ClassFilters, { filters: [] }) createReviewModel(builder) createAction(builder, { ...viewTemplates.open, target: recruit.class.Vacancy, context: { mode: ['browser', 'context'], group: 'create' }, action: workbench.actionImpl.Navigate, actionProps: { mode: 'space' } }) createAction(builder, { ...viewTemplates.open, target: recruit.class.ApplicantMatch, context: { mode: ['browser', 'context'], group: 'create' } }) function createGotoSpecialAction (builder: Builder, id: string, key: KeyBinding, label: IntlString): void { createNavigateAction(builder, key, label, recruit.app.Recruit as Ref, { application: recruitId, mode: 'special', special: id }) } createGotoSpecialAction(builder, talentsId, 'g->e', recruit.string.GotoTalents) createGotoSpecialAction(builder, vacanciesId, 'g->v', recruit.string.GotoVacancies) createGotoSpecialAction(builder, skillsId, 'g->s', recruit.string.GotoSkills) createGotoSpecialAction(builder, myApplicationsId, 'g->h', recruit.string.GotoMyApplications) createGotoSpecialAction(builder, candidatesId, 'g->a', recruit.string.GotoApplicants) createAction(builder, { action: workbench.actionImpl.Navigate, actionProps: { mode: 'app', application: recruitId, special: talentsId }, label: recruit.string.GotoRecruitApplication, icon: view.icon.ArrowRight, input: 'none', category: view.category.Navigation, target: core.class.Doc, context: { mode: ['workbench', 'browser', 'editor', 'panel', 'popup'] } }) createAction(builder, { action: view.actionImpl.ValueSelector, actionPopup: view.component.ValueSelector, actionProps: { attribute: 'assignee', _class: contact.class.Employee, query: {}, placeholder: recruit.string.AssignRecruiter }, label: recruit.string.AssignRecruiter, icon: contact.icon.Person, keyBinding: [], input: 'none', category: recruit.category.Recruit, target: recruit.class.Applicant, context: { mode: ['context'], application: recruit.app.Recruit, group: 'edit' } }) createAction(builder, { action: view.actionImpl.ValueSelector, actionPopup: view.component.ValueSelector, actionProps: { attribute: 'state', _class: task.class.State, query: {}, searchField: 'name', // should match space fillQuery: { space: 'space' }, // Only apply for same vacancy docMatches: ['space'], placeholder: task.string.TaskState }, label: task.string.TaskState, icon: task.icon.TaskState, keyBinding: [], input: 'none', category: recruit.category.Recruit, target: recruit.class.Applicant, context: { mode: ['context'], application: recruit.app.Recruit, group: 'edit' } }) createAction(builder, { action: view.actionImpl.ValueSelector, actionPopup: view.component.ValueSelector, actionProps: { attribute: 'doneState', _class: task.class.DoneState, query: {}, searchField: 'name', // should match space fillQuery: { space: 'space' }, // Only apply for same vacancy docMatches: ['space'], placeholder: task.string.DoneState }, label: task.string.DoneState, icon: task.icon.TaskState, keyBinding: [], input: 'none', category: recruit.category.Recruit, target: recruit.class.Applicant, context: { mode: ['context'], application: recruit.app.Recruit, group: 'edit' } }) createAction( builder, { action: view.actionImpl.CopyTextToClipboard, actionProps: { textProvider: recruit.function.IdProvider }, label: recruit.string.CopyId, icon: view.icon.CopyId, keyBinding: [], input: 'none', category: recruit.category.Recruit, target: recruit.class.Applicant, context: { mode: ['context', 'browser'], application: recruit.app.Recruit, group: 'copy' } }, recruit.action.CopyApplicationId ) createAction( builder, { action: view.actionImpl.CopyTextToClipboard, actionProps: { textProvider: recruit.function.GetObjectLink }, label: recruit.string.CopyLink, icon: view.icon.CopyLink, keyBinding: [], input: 'none', category: recruit.category.Recruit, target: recruit.class.Applicant, context: { mode: ['context', 'browser'], application: recruit.app.Recruit, group: 'copy' } }, recruit.action.CopyApplicationLink ) createAction( builder, { action: view.actionImpl.CopyTextToClipboard, actionProps: { textProvider: recruit.function.GetObjectLink }, label: recruit.string.CopyLink, icon: view.icon.CopyLink, keyBinding: [], input: 'none', category: recruit.category.Recruit, target: recruit.class.Vacancy, context: { mode: ['context', 'browser'], application: recruit.app.Recruit, group: 'copy' } }, recruit.action.CopyCandidateLink ) builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.AttributeFilter, { component: recruit.component.ApplicantFilter }) builder.mixin(recruit.class.Applicant, core.class.Class, notification.mixin.NotificationObjectPresenter, { presenter: recruit.component.NotificationApplicantPresenter }) builder.createDoc( notification.class.NotificationGroup, core.space.Model, { label: recruit.string.Application, icon: recruit.icon.Application, objectClass: recruit.class.Applicant }, recruit.ids.ApplicationNotificationGroup ) builder.createDoc( notification.class.NotificationType, core.space.Model, { hidden: false, generated: false, label: task.string.AssignedToMe, group: recruit.ids.ApplicationNotificationGroup, field: 'assignee', txClasses: [core.class.TxCreateDoc, core.class.TxUpdateDoc], objectClass: recruit.class.Applicant, templates: { textTemplate: '{doc} was assigned to you by {sender}', htmlTemplate: '

{doc} was assigned to you by {sender}

', subjectTemplate: '{doc} was assigned to you' }, providers: { [notification.providers.PlatformNotification]: true, [notification.providers.EmailNotification]: true } }, recruit.ids.AssigneeNotification ) generateClassNotificationTypes( builder, recruit.class.Applicant, recruit.ids.ApplicationNotificationGroup, [], ['comments', 'state', 'doneState', 'dueDate'] ) builder.createDoc( notification.class.NotificationGroup, core.space.Model, { label: recruit.string.Vacancy, icon: recruit.icon.Vacancy, objectClass: recruit.class.Vacancy }, recruit.ids.VacancyNotificationGroup ) builder.createDoc( notification.class.NotificationType, core.space.Model, { hidden: false, generated: false, label: recruit.string.CreateApplication, group: recruit.ids.VacancyNotificationGroup, field: 'space', txClasses: [core.class.TxCreateDoc, core.class.TxUpdateDoc], objectClass: recruit.class.Applicant, spaceSubscribe: true, providers: { [notification.providers.PlatformNotification]: false } }, recruit.ids.ApplicationCreateNotification ) generateClassNotificationTypes(builder, recruit.class.Vacancy, recruit.ids.VacancyNotificationGroup, [], ['comments']) builder.createDoc( notification.class.NotificationGroup, core.space.Model, { label: recruit.string.Talent, icon: recruit.icon.CreateCandidate, objectClass: recruit.mixin.Candidate }, recruit.ids.CandidateNotificationGroup ) generateClassNotificationTypes( builder, recruit.mixin.Candidate, recruit.ids.CandidateNotificationGroup, ['vacancyMatch'], ['comments'] ) builder.createDoc( view.class.FilterMode, core.space.Model, { label: recruit.string.HasActiveApplicant, result: recruit.function.HasActiveApplicant, disableValueSelector: true }, recruit.filter.HasActive ) builder.createDoc( view.class.FilterMode, core.space.Model, { label: recruit.string.HasNoActiveApplicant, result: recruit.function.HasNoActiveApplicant, disableValueSelector: true }, recruit.filter.NoActive ) builder.createDoc( view.class.FilterMode, core.space.Model, { label: recruit.string.NoneApplications, result: recruit.function.NoneApplications, disableValueSelector: true }, recruit.filter.None ) // Allow to use fuzzy search for mixins builder.mixin(recruit.class.Vacancy, core.class.Class, core.mixin.FullTextSearchContext, { fullTextSummary: true, propagate: [] }) builder.mixin(recruit.mixin.Candidate, core.class.Class, core.mixin.FullTextSearchContext, { fullTextSummary: true, propagate: [recruit.class.Applicant] }) // Allow to use fuzzy search for mixins builder.mixin(recruit.class.Applicant, core.class.Class, core.mixin.FullTextSearchContext, { fullTextSummary: true, forceIndex: true, propagate: [] }) createAction(builder, { label: recruit.string.MatchVacancy, icon: recruit.icon.Vacancy, action: view.actionImpl.ShowPopup, actionProps: { component: recruit.component.MatchVacancy, element: 'top', fillProps: { _objects: 'objects' } }, input: 'any', category: recruit.category.Recruit, keyBinding: [], target: recruit.mixin.Candidate, context: { mode: ['context', 'browser'], group: 'create' } }) builder.mixin(recruit.mixin.Candidate, core.class.Class, view.mixin.ObjectEditorFooter, { editor: tracker.component.RelatedIssuesSection, props: { label: recruit.string.RelatedIssues } }) builder.mixin(recruit.class.Vacancy, core.class.Class, view.mixin.ObjectEditorFooter, { editor: tracker.component.RelatedIssuesSection, props: { label: recruit.string.RelatedIssues } }) builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.ObjectEditorFooter, { editor: tracker.component.RelatedIssuesSection, props: { label: recruit.string.RelatedIssues } }) createAction( builder, { label: view.string.Move, action: recruit.actionImpl.MoveApplicant, icon: view.icon.Move, input: 'any', category: view.category.General, target: recruit.class.Applicant, context: { mode: ['context', 'browser'], group: 'tools' }, override: [task.action.Move] }, recruit.action.MoveApplicant ) }