diff --git a/dev/generator/src/issues.ts b/dev/generator/src/issues.ts index d2c294b703..4b2e74e5b9 100644 --- a/dev/generator/src/issues.ts +++ b/dev/generator/src/issues.ts @@ -11,9 +11,10 @@ import core, { TxOperations, WorkspaceId } from '@hcengineering/core' -import tracker, { calcRank, Issue, IssuePriority, IssueStatus } from '@hcengineering/tracker' +import tracker, { Issue, IssuePriority, IssueStatus } from '@hcengineering/tracker' import { connect } from './connect' +import { calcRank } from '@hcengineering/task' let objectId: Ref = generateId() const space = tracker.project.DefaultProject @@ -26,7 +27,6 @@ const object: AttachedData = { milestone: null, number: 0, rank: '', - doneState: null, status: '' as Ref, priority: IssuePriority.NoPriority, dueDate: null, @@ -88,7 +88,6 @@ async function genIssue (client: TxOperations, statuses: Ref[]): Pr component: object.component, milestone: object.milestone, number: (incResult as any).object.sequence, - doneState: null, status: faker.random.arrayElement(statuses), priority: faker.random.arrayElement(Object.values(IssuePriority)) as IssuePriority, rank: calcRank(lastOne, undefined), diff --git a/dev/generator/src/kanban.ts b/dev/generator/src/kanban.ts index 5f66f51303..71a0645941 100644 --- a/dev/generator/src/kanban.ts +++ b/dev/generator/src/kanban.ts @@ -1,12 +1,12 @@ -import { MeasureContext, Ref, TxOperations } from '@hcengineering/core' -import task, { DoneState, SpaceWithStates, State } from '@hcengineering/task' +import core, { MeasureContext, Ref, Status, TxOperations } from '@hcengineering/core' +import task, { Project } from '@hcengineering/task' import { findOrUpdate } from './utils' export async function createUpdateSpaceKanban ( ctx: MeasureContext, - spaceId: Ref, + spaceId: Ref, client: TxOperations -): Promise<[Ref[], Ref[]]> { +): Promise[]> { const rawStates = [ { color: 9, name: 'Initial' }, { color: 10, name: 'Intermidiate' }, @@ -14,35 +14,40 @@ export async function createUpdateSpaceKanban ( { color: 0, name: 'Done' }, { color: 11, name: 'Invalid' } ] - const states: Array> = [] - for (const st of rawStates) { - const sid = ('generated-' + spaceId + '.state.' + st.name.toLowerCase().replace(' ', '_')) as Ref - - await ctx.with('find-or-update', {}, (ctx) => - findOrUpdate(ctx, client, task.space.Statuses, task.class.State, sid, { - ofAttribute: task.attribute.State, - name: st.name, - color: st.color - }) - ) - states.push(sid) - } - - const done: Array> = [] - const doneStates = [ - { class: task.class.WonState, name: 'Won' }, - { class: task.class.LostState, name: 'Lost' } + { category: task.statusCategory.Won, name: 'Won' }, + { category: task.statusCategory.Lost, name: 'Lost' } ] - for (const st of doneStates) { - const sid = `generated-${spaceId}.done-state.${st.name.toLowerCase().replace(' ', '_')}` as Ref - await ctx.with('gen-done-state', {}, (ctx) => - findOrUpdate(ctx, client, task.space.Statuses, st.class, sid, { - ofAttribute: task.attribute.DoneState, - name: st.name - }) - ) - done.push(sid) - } - return [states, done] + const states: Ref[] = [] + await Promise.all( + rawStates.map(async (st, i) => { + const sid = ('generated-' + spaceId + '.state.' + st.name.toLowerCase().replace(' ', '_')) as Ref + + await ctx.with('find-or-update', {}, (ctx) => + findOrUpdate(ctx, client, task.space.Statuses, core.class.Status, sid, { + ofAttribute: task.attribute.State, + name: st.name, + color: st.color, + category: task.statusCategory.Active + }) + ) + states.push(sid) + }) + ) + + await Promise.all( + doneStates.map(async (st, i) => { + const sid = `generated-${spaceId}.state.${st.name.toLowerCase().replace(' ', '_')}` as Ref + await ctx.with('gen-done-state', {}, (ctx) => + findOrUpdate(ctx, client, task.space.Statuses, core.class.Status, sid, { + ofAttribute: task.attribute.State, + name: st.name, + category: st.category + }) + ) + states.push(sid) + }) + ) + + return states } diff --git a/dev/generator/src/recruit.ts b/dev/generator/src/recruit.ts index 8f6ad02bc8..53c397cc0b 100644 --- a/dev/generator/src/recruit.ts +++ b/dev/generator/src/recruit.ts @@ -6,6 +6,7 @@ import core, { MeasureMetricsContext, MixinUpdate, Ref, + Status, TxOperations, WorkspaceId, generateId, @@ -14,7 +15,7 @@ import core, { import { MinioService } from '@hcengineering/minio' import recruit from '@hcengineering/model-recruit' import { Applicant, Candidate, Vacancy } from '@hcengineering/recruit' -import { State, genRanks } from '@hcengineering/task' +import task, { ProjectType, genRanks } from '@hcengineering/task' import faker from 'faker' import jpeg, { BufferRet } from 'jpeg-js' import { AttachmentOptions, addAttachments } from './attachments' @@ -87,8 +88,26 @@ async function genVacansyApplicants ( candidates: Ref[], emoloyeeIds: Ref[] ): Promise { - const [states, doneStates] = await ctx.with('create-kanbad', {}, (ctx) => - createUpdateSpaceKanban(ctx, vacancyId, client) + const vacancyId = (options.random ? `vacancy-${generateId()}-${i}` : `vacancy-genid-${i}`) as Ref + + const typeId = (options.random ? `vacancy-type-${generateId()}-${i}` : `vacancy-type-genid-${i}`) as Ref + + const states = await ctx.with('create-kanbad', {}, (ctx) => createUpdateSpaceKanban(ctx, vacancyId, client)) + const type: Data = { + name: faker.name.title(), + description: faker.lorem.sentences(2), + shortDescription: faker.lorem.sentences(1), + category: recruit.category.VacancyTypeCategories, + private: false, + members: [], + archived: false, + statuses: states.map((s) => { + return { _id: s } + }) + } + + await ctx.with('update', {}, (ctx) => + findOrUpdate(ctx, client, core.space.Space, task.class.ProjectType, typeId, type) ) const vacancy: Data = { @@ -100,10 +119,8 @@ async function genVacansyApplicants ( number: faker.datatype.number(), private: false, archived: false, - states, - doneStates + type: typeId } - const vacancyId = (options.random ? `vacancy-${generateId()}-${i}` : `vacancy-genid-${i}`) as Ref console.log('Creating vacandy', vacancy.name) @@ -134,10 +151,11 @@ async function genVacansyApplicants ( const applicantsForCount = options.applicants.min + faker.datatype.number(options.applicants.max) const applicantsFor = faker.random.arrayElements(candidates, applicantsForCount) - const rankGen = genRanks(candidates.length) - for (const candidateId of applicantsFor) { + const ranks = genRanks(candidates.length) + for (let index = 0; index < applicantsFor.length; index++) { + const candidateId = applicantsFor[index] await ctx.with('applicant', {}, (ctx) => - genApplicant(ctx, vacancyId, candidateId, emoloyeeIds, states, client, options, minio, workspaceId, rankGen) + genApplicant(ctx, vacancyId, candidateId, emoloyeeIds, states, client, options, minio, workspaceId, ranks[i]) ) } } @@ -147,22 +165,20 @@ async function genApplicant ( vacancyId: Ref, candidateId: Ref, emoloyeeIds: Ref[], - states: Ref[], + states: Ref[], client: TxOperations, options: RecruitOptions, minio: MinioService, workspaceId: WorkspaceId, - rankGen: Generator + rank: string ): Promise { const applicantId = `vacancy-${vacancyId}-${candidateId}` as Ref - const rank = rankGen.next().value const applicant: AttachedData = { number: faker.datatype.number(), assignee: faker.random.arrayElement(emoloyeeIds), status: faker.random.arrayElement(states), - doneState: null, - rank: rank as string, + rank, startDate: null, dueDate: null } diff --git a/models/board/src/index.ts b/models/board/src/index.ts index 3d80c1d1e1..553309242e 100644 --- a/models/board/src/index.ts +++ b/models/board/src/index.ts @@ -16,7 +16,7 @@ // To help typescript locate view plugin properly import { Board, boardId, Card, CardCover, CommonBoardPreference, MenuPage } from '@hcengineering/board' import type { Employee } from '@hcengineering/contact' -import { DOMAIN_MODEL, IndexKind, Markup, Ref, Timestamp, Type } from '@hcengineering/core' +import { DOMAIN_MODEL, IndexKind, Markup, Ref, Status, Timestamp, Type } from '@hcengineering/core' import { ArrOf, Builder, @@ -34,11 +34,10 @@ import contact from '@hcengineering/model-contact' import core, { TDoc, TType } from '@hcengineering/model-core' import preference, { TPreference } from '@hcengineering/model-preference' import tags from '@hcengineering/model-tags' -import task, { actionTemplates as taskActionTemplates, TSpaceWithStates, TTask } from '@hcengineering/model-task' +import task, { actionTemplates as taskActionTemplates, TProject, TTask } from '@hcengineering/model-task' import view, { actionTemplates, createAction, actionTemplates as viewTemplates } from '@hcengineering/model-view' import workbench, { Application } from '@hcengineering/model-workbench' import { IntlString } from '@hcengineering/platform' -import { DoneState, State } from '@hcengineering/task' import type { AnyComponent } from '@hcengineering/ui' import board from './plugin' @@ -46,9 +45,9 @@ export { boardId } from '@hcengineering/board' export { boardOperation } from './migration' export { default } from './plugin' -@Model(board.class.Board, task.class.SpaceWithStates) +@Model(board.class.Board, task.class.Project) @UX(board.string.Board, board.icon.Board) -export class TBoard extends TSpaceWithStates implements Board { +export class TBoard extends TProject implements Board { color!: number background!: string } @@ -100,11 +99,8 @@ export class TCard extends TTask implements Card { @Prop(TypeDate(), task.string.StartDate) startDate!: Timestamp | null - @Prop(TypeRef(task.class.State), task.string.TaskState, { _id: board.attribute.State }) - declare status: Ref - - @Prop(TypeRef(task.class.DoneState), task.string.TaskStateDone, { _id: board.attribute.DoneState }) - declare doneState: Ref + @Prop(TypeRef(core.class.Status), task.string.TaskState, { _id: board.attribute.State }) + declare status: Ref } @Model(board.class.MenuPage, core.class.Doc, DOMAIN_MODEL) @@ -178,10 +174,6 @@ export function createModel (builder: Builder): void { { ...taskActionTemplates.editStatus, target: board.class.Board, - actionProps: { - ofAttribute: board.attribute.State, - doneOfAttribute: board.attribute.DoneState - }, query: { archived: false }, @@ -287,16 +279,6 @@ export function createModel (builder: Builder): void { board.viewlet.Table ) - builder.createDoc( - task.class.WonState, - core.space.Model, - { - ofAttribute: task.attribute.DoneState, - name: board.string.Completed - }, - board.state.Completed - ) - // card actions createAction( builder, @@ -517,4 +499,18 @@ export function createModel (builder: Builder): void { mode: 'space' } }) + + builder.createDoc( + task.class.ProjectTypeCategory, + core.space.Model, + { + name: board.string.Boards, + description: board.string.ManageBoardStatuses, + icon: board.component.TemplatesIcon, + attachedToClass: board.class.Board, + statusClass: core.class.Status, + statusCategories: [task.statusCategory.Active, task.statusCategory.Won, task.statusCategory.Lost] + }, + board.category.BoardType + ) } diff --git a/models/board/src/migration.ts b/models/board/src/migration.ts index 893d45378d..ab472b2003 100644 --- a/models/board/src/migration.ts +++ b/models/board/src/migration.ts @@ -16,45 +16,18 @@ import { Ref, TxOperations } from '@hcengineering/core' import { MigrateOperation, MigrationClient, MigrationUpgradeClient, createOrUpdate } from '@hcengineering/model' import core from '@hcengineering/model-core' -import { createKanbanTemplate, createSequence } from '@hcengineering/model-task' +import { createProjectType, createSequence } from '@hcengineering/model-task' import tags from '@hcengineering/tags' -import task, { KanbanTemplate, createStates } from '@hcengineering/task' +import task, { ProjectType } from '@hcengineering/task' import board from './plugin' +import { PaletteColorIndexes } from '@hcengineering/ui/src/colors' async function createSpace (tx: TxOperations): Promise { - const currentTemplate = await tx.findOne(task.class.KanbanTemplateSpace, { - _id: board.space.BoardTemplates - }) - if (currentTemplate === undefined) { - await tx.createDoc( - task.class.KanbanTemplateSpace, - core.space.Space, - { - name: board.string.Boards, - description: board.string.ManageBoardStatuses, - icon: board.component.TemplatesIcon, - private: false, - archived: false, - members: [], - attachedToClass: board.class.Board, - ofAttribute: board.attribute.State, - doneAttribute: board.attribute.DoneState - }, - board.space.BoardTemplates - ) - } else if (currentTemplate.ofAttribute === undefined) { - await tx.update(currentTemplate, { - ofAttribute: board.attribute.State, - doneAttribute: board.attribute.DoneState - }) - } - const current = await tx.findOne(core.class.Space, { _id: board.space.DefaultBoard }) if (current === undefined) { - const defaultTmpl = await createDefaultKanbanTemplate(tx) - const [states, doneStates] = await createStates(tx, board.attribute.State, board.attribute.DoneState, defaultTmpl) + const defaultType = await createDefaultProjectType(tx) await tx.createDoc( board.class.Board, core.space.Space, @@ -64,39 +37,45 @@ async function createSpace (tx: TxOperations): Promise { private: false, archived: false, members: [], - states, - doneStates + type: defaultType }, board.space.DefaultBoard ) } } -async function createDefaultKanbanTemplate (tx: TxOperations): Promise> { - const defaultKanban = { - states: [ - { color: 9, name: 'To do' }, - { color: 9, name: 'Done' } - ], - doneStates: [ - { isWon: true, name: 'Won' }, - { isWon: false, name: 'Lost' } - ] - } - - return await createKanbanTemplate( +async function createDefaultProjectType (tx: TxOperations): Promise> { + return await createProjectType( tx, { - kanbanId: board.template.DefaultBoard, - space: board.space.BoardTemplates, - title: 'Default board', - states: defaultKanban.states, - doneStates: defaultKanban.doneStates + name: 'Default board', + category: board.category.BoardType, + description: '' }, - board.attribute.State, - board.attribute.DoneState + [ + { + color: PaletteColorIndexes.Blueberry, + name: 'To do', + category: task.statusCategory.Active, + ofAttribute: board.attribute.State + }, + { + color: PaletteColorIndexes.Arctic, + name: 'Done', + category: task.statusCategory.Active, + ofAttribute: board.attribute.State + }, + { + color: PaletteColorIndexes.Grass, + name: 'Completed', + category: board.statusCategory.Completed, + ofAttribute: board.attribute.State + } + ], + board.template.DefaultBoard ) } + async function createDefaults (tx: TxOperations): Promise { await createSpace(tx) await createSequence(tx, board.class.Card) @@ -115,21 +94,10 @@ async function createDefaults (tx: TxOperations): Promise { ) } -async function fixTemplateSpace (tx: TxOperations): Promise { - const templateSpace = await tx.findOne(task.class.KanbanTemplateSpace, { _id: board.space.BoardTemplates }) - if (templateSpace !== undefined && templateSpace?.attachedToClass === undefined) { - await tx.update(templateSpace, { attachedToClass: board.class.Board }) - } -} - -async function migrateLabels (client: MigrationClient): Promise {} export const boardOperation: MigrateOperation = { - async migrate (client: MigrationClient): Promise { - await Promise.all([migrateLabels(client)]) - }, + async migrate (client: MigrationClient): Promise {}, async upgrade (client: MigrationUpgradeClient): Promise { const ops = new TxOperations(client, core.account.System) await createDefaults(ops) - await fixTemplateSpace(ops) } } diff --git a/models/board/src/plugin.ts b/models/board/src/plugin.ts index 6dbcb82901..21f047e37c 100644 --- a/models/board/src/plugin.ts +++ b/models/board/src/plugin.ts @@ -18,7 +18,7 @@ import { Board, boardId } from '@hcengineering/board' import board from '@hcengineering/board-resources/src/plugin' import type { Ref } from '@hcengineering/core' import { IntlString, mergeIds } from '@hcengineering/platform' -import { KanbanTemplate, Sequence } from '@hcengineering/task' +import { ProjectType, Sequence } from '@hcengineering/task' import type { AnyComponent } from '@hcengineering/ui' import { Action, ViewAction, Viewlet, ViewletDescriptor } from '@hcengineering/view' @@ -45,7 +45,7 @@ export default mergeIds(boardId, board, { DefaultBoard: '' as Ref }, template: { - DefaultBoard: '' as Ref + DefaultBoard: '' as Ref }, ids: { Sequence: '' as Ref diff --git a/models/calendar/src/index.ts b/models/calendar/src/index.ts index 2656f45e28..17df59e3e7 100644 --- a/models/calendar/src/index.ts +++ b/models/calendar/src/index.ts @@ -46,7 +46,7 @@ import { import attachment from '@hcengineering/model-attachment' import contact from '@hcengineering/model-contact' import core, { TAttachedDoc, TClass } from '@hcengineering/model-core' -import { TSpaceWithStates } from '@hcengineering/model-task' +import { TProject } from '@hcengineering/model-task' import view, { createAction } from '@hcengineering/model-view' import notification from '@hcengineering/notification' import setting from '@hcengineering/setting' @@ -61,7 +61,7 @@ export const DOMAIN_CALENDAR = 'calendar' as Domain @Model(calendar.class.Calendar, core.class.Space) @UX(calendar.string.Calendar, calendar.icon.Calendar) -export class TCalendar extends TSpaceWithStates implements Calendar { +export class TCalendar extends TProject implements Calendar { visibility!: Visibility } diff --git a/models/core/src/status.ts b/models/core/src/status.ts index 12f3768b0e..dad7673f42 100644 --- a/models/core/src/status.ts +++ b/models/core/src/status.ts @@ -38,6 +38,8 @@ export class TStatus extends TDoc implements Status { @Prop(TypeString(), core.string.Description) description!: string + + rank!: string } @Model(core.class.StatusCategory, core.class.Doc, DOMAIN_MODEL) diff --git a/models/lead/src/index.ts b/models/lead/src/index.ts index 030291c2d2..e6b0363fe5 100644 --- a/models/lead/src/index.ts +++ b/models/lead/src/index.ts @@ -15,7 +15,7 @@ // To help typescript locate view plugin properly import type { Employee } from '@hcengineering/contact' -import { FindOptions, IndexKind, Ref, SortingOrder, Timestamp } from '@hcengineering/core' +import { FindOptions, IndexKind, Ref, SortingOrder, Status, Timestamp } from '@hcengineering/core' import { Customer, Funnel, Lead, leadId } from '@hcengineering/lead' import { Builder, @@ -36,13 +36,12 @@ import chunter from '@hcengineering/model-chunter' import contact, { TContact } from '@hcengineering/model-contact' import core from '@hcengineering/model-core' import { generateClassNotificationTypes } from '@hcengineering/model-notification' -import task, { TSpaceWithStates, TTask, actionTemplates } from '@hcengineering/model-task' +import task, { TProject, TTask, actionTemplates } from '@hcengineering/model-task' import tracker from '@hcengineering/model-tracker' import view, { createAction, actionTemplates as viewTemplates } from '@hcengineering/model-view' import workbench from '@hcengineering/model-workbench' import notification from '@hcengineering/notification' import setting from '@hcengineering/setting' -import { DoneState, State } from '@hcengineering/task' import { ViewOptionsModel } from '@hcengineering/view' import lead from './plugin' @@ -50,9 +49,9 @@ export { leadId } from '@hcengineering/lead' export { leadOperation } from './migration' export { default } from './plugin' -@Model(lead.class.Funnel, task.class.SpaceWithStates) +@Model(lead.class.Funnel, task.class.Project) @UX(lead.string.Funnel, lead.icon.Funnel) -export class TFunnel extends TSpaceWithStates implements Funnel { +export class TFunnel extends TProject implements Funnel { @Prop(TypeMarkup(), lead.string.FullDescription) @Index(IndexKind.FullText) fullDescription?: string @@ -81,11 +80,8 @@ export class TLead extends TTask implements Lead { @Prop(TypeRef(contact.mixin.Employee), lead.string.Assignee) declare assignee: Ref | null - @Prop(TypeRef(task.class.State), task.string.TaskState, { _id: lead.attribute.State }) - declare status: Ref - - @Prop(TypeRef(task.class.DoneState), task.string.TaskStateDone, { _id: lead.attribute.DoneState }) - declare doneState: Ref + @Prop(TypeRef(core.class.Status), task.string.TaskState, { _id: lead.attribute.State }) + declare status: Ref declare space: Ref } @@ -264,7 +260,6 @@ export function createModel (builder: Builder): void { label: tracker.string.Issues }, 'status', - 'doneState', 'attachments', 'comments', 'modifiedOn', @@ -382,10 +377,6 @@ export function createModel (builder: Builder): void { { ...actionTemplates.editStatus, target: lead.class.Funnel, - actionProps: { - ofAttribute: lead.attribute.State, - doneOfAttribute: lead.attribute.DoneState - }, query: { archived: false }, @@ -433,13 +424,7 @@ export function createModel (builder: Builder): void { lead.ids.AssigneeNotification ) - generateClassNotificationTypes( - builder, - lead.class.Lead, - lead.ids.LeadNotificationGroup, - [], - ['comments', 'status', 'doneState'] - ) + generateClassNotificationTypes(builder, lead.class.Lead, lead.ids.LeadNotificationGroup, [], ['comments', 'status']) builder.createDoc( notification.class.NotificationGroup, @@ -670,4 +655,18 @@ export function createModel (builder: Builder): void { group: 'associate' } }) + + builder.createDoc( + task.class.ProjectTypeCategory, + core.space.Model, + { + name: lead.string.Funnels, + description: lead.string.ManageFunnelStatuses, + icon: lead.component.TemplatesIcon, + attachedToClass: lead.class.Funnel, + statusClass: core.class.Status, + statusCategories: [task.statusCategory.Active, task.statusCategory.Won, task.statusCategory.Lost] + }, + lead.category.FunnelTypeCategory + ) } diff --git a/models/lead/src/migration.ts b/models/lead/src/migration.ts index d599a028d6..cc4bb10ca3 100644 --- a/models/lead/src/migration.ts +++ b/models/lead/src/migration.ts @@ -13,50 +13,64 @@ // limitations under the License. // -import { Ref, TxOperations } from '@hcengineering/core' +import { TxOperations } from '@hcengineering/core' import { leadId } from '@hcengineering/lead' import { MigrateOperation, MigrationClient, MigrationUpgradeClient, tryUpgrade } from '@hcengineering/model' import core from '@hcengineering/model-core' -import { createKanbanTemplate, createSequence } from '@hcengineering/model-task' +import { createProjectType, createSequence } from '@hcengineering/model-task' import tracker from '@hcengineering/model-tracker' -import task, { KanbanTemplate, createStates } from '@hcengineering/task' +import task from '@hcengineering/task' import { PaletteColorIndexes } from '@hcengineering/ui/src/colors' import lead from './plugin' async function createSpace (tx: TxOperations): Promise { - const currentTemplate = await tx.findOne(task.class.KanbanTemplateSpace, { - _id: lead.space.FunnelTemplates - }) - if (currentTemplate === undefined) { - await tx.createDoc( - task.class.KanbanTemplateSpace, - core.space.Space, - { - name: lead.string.Funnels, - description: lead.string.ManageFunnelStatuses, - icon: lead.component.TemplatesIcon, - private: false, - members: [], - archived: false, - attachedToClass: lead.class.Funnel, - ofAttribute: lead.attribute.State, - doneAttribute: lead.attribute.DoneState - }, - lead.space.FunnelTemplates - ) - } else if (currentTemplate.ofAttribute === undefined) { - await tx.update(currentTemplate, { - ofAttribute: lead.attribute.State, - doneAttribute: lead.attribute.DoneState - }) - } - const current = await tx.findOne(core.class.Space, { _id: lead.space.DefaultFunnel }) if (current === undefined) { - const defaultTmpl = await createDefaultKanbanTemplate(tx) - const [states, doneStates] = await createStates(tx, lead.attribute.State, lead.attribute.DoneState, defaultTmpl) + const type = await createProjectType( + tx, + { + name: 'Default funnel', + category: lead.category.FunnelTypeCategory, + description: '' + }, + [ + { + color: PaletteColorIndexes.Coin, + name: 'Incoming', + ofAttribute: lead.attribute.State, + category: task.statusCategory.Active + }, + { + color: PaletteColorIndexes.Arctic, + name: 'Negotation', + ofAttribute: lead.attribute.State, + category: task.statusCategory.Active + }, + { + color: PaletteColorIndexes.Watermelon, + name: 'Offer preparing', + ofAttribute: lead.attribute.State, + category: task.statusCategory.Active + }, + { + color: PaletteColorIndexes.Orange, + name: 'Make a decision', + ofAttribute: lead.attribute.State, + category: task.statusCategory.Active + }, + { + color: PaletteColorIndexes.Ocean, + name: 'Contract conclusion', + ofAttribute: lead.attribute.State, + category: task.statusCategory.Active + }, + { name: 'Won', ofAttribute: lead.attribute.State, category: task.statusCategory.Won }, + { name: 'Lost', ofAttribute: lead.attribute.State, category: task.statusCategory.Lost } + ], + lead.template.DefaultFunnel + ) await tx.createDoc( lead.class.Funnel, core.space.Space, @@ -66,55 +80,16 @@ async function createSpace (tx: TxOperations): Promise { private: false, archived: false, members: [], - states, - doneStates + type }, lead.space.DefaultFunnel ) } } -async function createDefaultKanbanTemplate (tx: TxOperations): Promise> { - const defaultKanban = { - states: [ - { color: PaletteColorIndexes.Coin, name: 'Incoming' }, - { color: PaletteColorIndexes.Arctic, name: 'Negotation' }, - { color: PaletteColorIndexes.Watermelon, name: 'Offer preparing' }, - { color: PaletteColorIndexes.Orange, name: 'Make a decision' }, - { color: PaletteColorIndexes.Ocean, name: 'Contract conclusion' }, - { color: PaletteColorIndexes.Grass, name: 'Done' } - ], - doneStates: [ - { isWon: true, name: 'Won' }, - { isWon: false, name: 'Lost' } - ] - } - - return await createKanbanTemplate( - tx, - { - kanbanId: lead.template.DefaultFunnel, - space: lead.space.FunnelTemplates, - title: 'Default funnel', - states: defaultKanban.states, - doneStates: defaultKanban.doneStates - }, - lead.attribute.State, - lead.attribute.DoneState - ) -} - -async function fixTemplateSpace (tx: TxOperations): Promise { - const templateSpace = await tx.findOne(task.class.KanbanTemplateSpace, { _id: lead.space.FunnelTemplates }) - if (templateSpace !== undefined && templateSpace?.attachedToClass === undefined) { - await tx.update(templateSpace, { attachedToClass: lead.class.Funnel }) - } -} - async function createDefaults (tx: TxOperations): Promise { await createSpace(tx) await createSequence(tx, lead.class.Lead) - await fixTemplateSpace(tx) } export const leadOperation: MigrateOperation = { diff --git a/models/lead/src/plugin.ts b/models/lead/src/plugin.ts index a350c4c216..f36ef2d24f 100644 --- a/models/lead/src/plugin.ts +++ b/models/lead/src/plugin.ts @@ -20,7 +20,7 @@ import lead from '@hcengineering/lead-resources/src/plugin' import { NotificationGroup, NotificationType } from '@hcengineering/notification' import type { IntlString } from '@hcengineering/platform' import { mergeIds } from '@hcengineering/platform' -import { KanbanTemplate } from '@hcengineering/task' +import { ProjectType } from '@hcengineering/task' import type { AnyComponent } from '@hcengineering/ui' import { Action, ActionCategory, Viewlet } from '@hcengineering/view' @@ -48,7 +48,7 @@ export default mergeIds(leadId, lead, { DefaultFunnel: '' as Ref }, template: { - DefaultFunnel: '' as Ref + DefaultFunnel: '' as Ref }, viewlet: { TableCustomer: '' as Ref, diff --git a/models/recruit/src/index.ts b/models/recruit/src/index.ts index 69ee19e558..9065695b2d 100644 --- a/models/recruit/src/index.ts +++ b/models/recruit/src/index.ts @@ -14,7 +14,7 @@ // import type { Employee, Organization } from '@hcengineering/contact' -import { IndexKind, Lookup, Ref, SortingOrder, Timestamp } from '@hcengineering/core' +import { IndexKind, Lookup, Ref, SortingOrder, Status, Timestamp } from '@hcengineering/core' import { Builder, Collection, @@ -39,7 +39,7 @@ 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 task, { DOMAIN_TASK, TProject, 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' @@ -55,7 +55,6 @@ import { recruitId } from '@hcengineering/recruit' import setting from '@hcengineering/setting' -import { DoneState, State } from '@hcengineering/task' import { KeyBinding, ViewOptionModel, ViewOptionsModel } from '@hcengineering/view' import recruit from './plugin' import { createReviewModel, reviewTableConfig, reviewTableOptions } from './review' @@ -65,9 +64,9 @@ export { recruitId } from '@hcengineering/recruit' export { recruitOperation } from './migration' export { default } from './plugin' -@Model(recruit.class.Vacancy, task.class.SpaceWithStates) +@Model(recruit.class.Vacancy, task.class.Project) @UX(recruit.string.Vacancy, recruit.icon.Vacancy, 'VCN', 'name') -export class TVacancy extends TSpaceWithStates implements Vacancy { +export class TVacancy extends TProject implements Vacancy { @Prop(TypeMarkup(), recruit.string.FullDescription) @Index(IndexKind.FullText) fullDescription?: string @@ -164,13 +163,9 @@ export class TApplicant extends TTask implements Applicant { @Index(IndexKind.Indexed) declare assignee: Ref | null - @Prop(TypeRef(task.class.State), task.string.TaskState, { _id: recruit.attribute.State }) + @Prop(TypeRef(core.class.Status), task.string.TaskState, { _id: recruit.attribute.State }) @Index(IndexKind.Indexed) - declare status: Ref - - @Prop(TypeRef(task.class.DoneState), task.string.TaskStateDone, { _id: recruit.attribute.DoneState }) - @Index(IndexKind.Indexed) - declare doneState: Ref + declare status: Ref } @Model(recruit.class.ApplicantMatch, core.class.AttachedDoc, DOMAIN_TASK) @@ -280,15 +275,15 @@ export function createModel (builder: Builder): void { }, { id: candidatesId, - component: workbench.component.SpecialView, + component: task.component.TypesView, 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, + category: recruit.category.VacancyTypeCategories, descriptors: [ view.viewlet.Table, view.viewlet.List, @@ -377,10 +372,6 @@ export function createModel (builder: Builder): void { { ...actionTemplates.editStatus, target: recruit.class.Vacancy, - actionProps: { - ofAttribute: recruit.attribute.State, - doneOfAttribute: recruit.attribute.DoneState - }, query: { archived: false }, @@ -454,7 +445,7 @@ export function createModel (builder: Builder): void { { attachTo: recruit.class.Applicant, descriptor: view.viewlet.Table, - config: ['', '$lookup.attachedTo', 'status', 'doneState', 'modifiedOn'], + config: ['', '$lookup.attachedTo', 'status', 'modifiedOn'], configOptions: { sortable: true }, @@ -469,7 +460,7 @@ export function createModel (builder: Builder): void { { attachTo: recruit.class.Applicant, descriptor: view.viewlet.Table, - config: ['', '$lookup.space.name', '$lookup.space.$lookup.company', 'status', 'comments', 'doneState'], + config: ['', '$lookup.space.name', '$lookup.space.$lookup.company', 'status', 'comments'], configOptions: { sortable: true }, @@ -582,7 +573,6 @@ export function createModel (builder: Builder): void { label: tracker.string.Issues }, 'status', - 'doneState', 'attachments', 'comments', 'modifiedOn', @@ -656,7 +646,7 @@ export function createModel (builder: Builder): void { sortable: true }, baseQuery: { - doneState: null, + isDone: { $ne: true }, '$lookup.space.archived': false } }, @@ -676,7 +666,7 @@ export function createModel (builder: Builder): void { } }, baseQuery: { - doneState: null, + isDone: { $ne: true }, '$lookup.space.archived': false } }, @@ -718,7 +708,7 @@ export function createModel (builder: Builder): void { const applicantViewOptions = (colors: boolean, hides: boolean): ViewOptionsModel => { const model: ViewOptionsModel = { - groupBy: ['status', 'doneState', 'assignee', 'space', 'createdBy', 'modifiedBy'], + groupBy: ['status', 'assignee', 'space', 'createdBy', 'modifiedBy'], orderBy: [ ['status', SortingOrder.Ascending], ['modifiedOn', SortingOrder.Descending], @@ -988,7 +978,7 @@ export function createModel (builder: Builder): void { descriptor: task.viewlet.Kanban, // eslint-disable-next-line @typescript-eslint/consistent-type-assertions baseQuery: { - doneState: null, + isDone: { $ne: true }, '$lookup.space.archived': false }, viewOptions: { @@ -1352,7 +1342,7 @@ export function createModel (builder: Builder): void { action: task.actionImpl.SelectStatus, actionPopup: task.component.StatusSelector, actionProps: { - _class: task.class.State, + _class: core.class.Status, ofAttribute: recruit.attribute.State, placeholder: task.string.TaskState }, @@ -1369,27 +1359,6 @@ export function createModel (builder: Builder): void { } }) - createAction(builder, { - action: task.actionImpl.SelectStatus, - actionPopup: task.component.StatusSelector, - actionProps: { - _class: task.class.DoneState, - ofAttribute: recruit.attribute.DoneState, - placeholder: task.string.DoneState - }, - label: task.string.DoneState, - icon: task.icon.TaskState, - keyBinding: ['keyS->keyD'], - input: 'any', - category: recruit.category.Recruit, - target: recruit.class.Applicant, - context: { - mode: ['context'], - application: recruit.app.Recruit, - group: 'edit' - } - }) - createAction( builder, { @@ -1502,7 +1471,7 @@ export function createModel (builder: Builder): void { recruit.class.Applicant, recruit.ids.ApplicationNotificationGroup, [], - ['comments', 'status', 'doneState', 'dueDate'] + ['comments', 'status', 'dueDate'] ) builder.createDoc( @@ -1691,4 +1660,19 @@ export function createModel (builder: Builder): void { }, recruit.action.GetTalentIds ) + + builder.createDoc( + task.class.ProjectTypeCategory, + core.space.Model, + { + name: recruit.string.Vacancies, + description: recruit.string.ManageVacancyStatuses, + icon: recruit.component.TemplatesIcon, + editor: recruit.component.VacancyTemplateEditor, + attachedToClass: recruit.class.Vacancy, + statusClass: core.class.Status, + statusCategories: [task.statusCategory.Active, task.statusCategory.Won, task.statusCategory.Lost] + }, + recruit.category.VacancyTypeCategories + ) } diff --git a/models/recruit/src/migration.ts b/models/recruit/src/migration.ts index 4416644444..20ebfc6eea 100644 --- a/models/recruit/src/migration.ts +++ b/models/recruit/src/migration.ts @@ -14,7 +14,7 @@ // import { getCategories } from '@anticrm/skillset' -import core, { Doc, Ref, Space, TxOperations } from '@hcengineering/core' +import core, { Ref, TxOperations } from '@hcengineering/core' import { MigrateOperation, MigrationClient, @@ -23,12 +23,12 @@ import { tryUpgrade } from '@hcengineering/model' import tags, { TagCategory } from '@hcengineering/model-tags' -import { createKanbanTemplate, createSequence } from '@hcengineering/model-task' -import task, { KanbanTemplate } from '@hcengineering/task' +import { createProjectType, createSequence } from '@hcengineering/model-task' +import tracker from '@hcengineering/model-tracker' +import { recruitId } from '@hcengineering/recruit' +import task, { ProjectType } from '@hcengineering/task' import { PaletteColorIndexes } from '@hcengineering/ui/src/colors' import recruit from './plugin' -import { recruitId } from '@hcengineering/recruit' -import tracker from '@hcengineering/model-tracker' export const recruitOperation: MigrateOperation = { async migrate (client: MigrationClient): Promise {}, @@ -36,15 +36,6 @@ export const recruitOperation: MigrateOperation = { const tx = new TxOperations(client, core.account.System) await createDefaults(tx) - await tryUpgrade(client, recruitId, [ - { - state: 'fix-template-space', - func: async (client) => { - await fixTemplateSpace(tx) - } - } - ]) - await tryUpgrade(client, recruitId, [ { state: 'related-targets', @@ -69,13 +60,6 @@ export const recruitOperation: MigrateOperation = { } } -async function fixTemplateSpace (tx: TxOperations): Promise { - const templateSpace = await tx.findOne(task.class.KanbanTemplateSpace, { _id: recruit.space.VacancyTemplates }) - if (templateSpace !== undefined && templateSpace?.attachedToClass === undefined) { - await tx.update(templateSpace, { attachedToClass: recruit.class.Vacancy }) - } -} - async function createDefaults (tx: TxOperations): Promise { await createSpaces(tx) @@ -116,33 +100,43 @@ async function createDefaults (tx: TxOperations): Promise { await createDefaultKanbanTemplate(tx) } -async function createDefaultKanbanTemplate (tx: TxOperations): Promise> { - const defaultKanban = { - states: [ - { color: PaletteColorIndexes.Coin, name: 'HR Interview' }, - { color: PaletteColorIndexes.Cerulean, name: 'Technical Interview' }, - { color: PaletteColorIndexes.Waterway, name: 'Test task' }, - { color: PaletteColorIndexes.Grass, name: 'Offer' } - ], - doneStates: [ - { isWon: true, name: 'Won' }, - { isWon: false, name: 'Lost' } - ] - } - - return await createKanbanTemplate( +async function createDefaultKanbanTemplate (tx: TxOperations): Promise> { + return await createProjectType( tx, { - kanbanId: recruit.template.DefaultVacancy, - space: recruit.space.VacancyTemplates as Ref as Ref, - title: 'Default vacancy', - description: '', - shortDescription: '', - states: defaultKanban.states, - doneStates: defaultKanban.doneStates + name: 'Default vacancy', + category: recruit.category.VacancyTypeCategories, + description: '' }, - recruit.attribute.State, - recruit.attribute.DoneState + [ + { + color: PaletteColorIndexes.Coin, + name: 'HR Interview', + ofAttribute: recruit.attribute.State, + category: task.statusCategory.Active + }, + { + color: PaletteColorIndexes.Cerulean, + name: 'Technical Interview', + ofAttribute: recruit.attribute.State, + category: task.statusCategory.Active + }, + { + color: PaletteColorIndexes.Waterway, + name: 'Test task', + ofAttribute: recruit.attribute.State, + category: task.statusCategory.Active + }, + { + color: PaletteColorIndexes.Grass, + name: 'Offer', + ofAttribute: recruit.attribute.State, + category: task.statusCategory.Active + }, + { name: 'Won', ofAttribute: recruit.attribute.State, category: task.statusCategory.Won }, + { name: 'Lost', ofAttribute: recruit.attribute.State, category: task.statusCategory.Lost } + ], + recruit.template.DefaultVacancy ) } @@ -184,32 +178,4 @@ async function createSpaces (tx: TxOperations): Promise { } else if (currentReviews.private) { await tx.update(currentReviews, { private: false }) } - - const currentTemplate = await tx.findOne(task.class.KanbanTemplateSpace, { - _id: recruit.space.VacancyTemplates - }) - if (currentTemplate === undefined) { - await tx.createDoc( - task.class.KanbanTemplateSpace, - core.space.Space, - { - name: recruit.string.Vacancies, - description: recruit.string.ManageVacancyStatuses, - icon: recruit.component.TemplatesIcon, - editor: recruit.component.VacancyTemplateEditor, - private: false, - members: [], - archived: false, - attachedToClass: recruit.class.Vacancy, - ofAttribute: recruit.attribute.State, - doneAttribute: recruit.attribute.DoneState - }, - recruit.space.VacancyTemplates - ) - } else if (currentTemplate.ofAttribute === undefined) { - await tx.update(currentTemplate, { - ofAttribute: recruit.attribute.State, - doneAttribute: recruit.attribute.DoneState - }) - } } diff --git a/models/recruit/src/plugin.ts b/models/recruit/src/plugin.ts index e42ee773a8..a95141a514 100644 --- a/models/recruit/src/plugin.ts +++ b/models/recruit/src/plugin.ts @@ -19,7 +19,7 @@ import type { IntlString, Resource, Status } from '@hcengineering/platform' import { mergeIds } from '@hcengineering/platform' import { recruitId } from '@hcengineering/recruit' import recruit from '@hcengineering/recruit-resources/src/plugin' -import { KanbanTemplate } from '@hcengineering/task' +import { ProjectType } from '@hcengineering/task' import type { AnyComponent, Location } from '@hcengineering/ui' import type { Action, ActionCategory, ViewAction, ViewQueryAction, Viewlet } from '@hcengineering/view' @@ -114,8 +114,7 @@ export default mergeIds(recruitId, recruit, { NotificationApplicantPresenter: '' as AnyComponent }, template: { - DefaultVacancy: '' as Ref, - Task: '' as Ref + DefaultVacancy: '' as Ref }, viewlet: { TableCandidate: '' as Ref, diff --git a/models/server-task/src/index.ts b/models/server-task/src/index.ts index 8542db0306..729248b906 100644 --- a/models/server-task/src/index.ts +++ b/models/server-task/src/index.ts @@ -13,32 +13,15 @@ // limitations under the License. // +import core from '@hcengineering/core/lib/component' import { Builder } from '@hcengineering/model' import serverCore from '@hcengineering/server-core' -import core from '@hcengineering/core/lib/component' import serverTask from '@hcengineering/server-task' -import task from '@hcengineering/task' export { serverTaskId } from '@hcengineering/server-task' export function createModel (builder: Builder): void { builder.createDoc(serverCore.class.Trigger, core.space.Model, { - trigger: serverTask.trigger.OnTemplateStateUpdate, - txMatch: { - _class: core.class.TxUpdateDoc, - objectClass: { - $in: [task.class.StateTemplate, task.class.LostStateTemplate, task.class.WonStateTemplate] - } - } - }) - - builder.createDoc(serverCore.class.Trigger, core.space.Model, { - trigger: serverTask.trigger.OnTemplateStateCreate, - txMatch: { - _class: core.class.TxCreateDoc, - objectClass: { - $in: [task.class.StateTemplate, task.class.LostStateTemplate, task.class.WonStateTemplate] - } - } + trigger: serverTask.trigger.OnStateUpdate }) } diff --git a/models/setting/src/index.ts b/models/setting/src/index.ts index bee8c4b970..dc982e6404 100644 --- a/models/setting/src/index.ts +++ b/models/setting/src/index.ts @@ -204,14 +204,14 @@ export function createModel (builder: Builder): void { core.space.Model, { name: 'statuses', - label: setting.string.ManageTemplates, + label: setting.string.ManageProjects, icon: task.icon.ManageTemplates, - component: setting.component.ManageTemplates, + component: setting.component.ManageProjects, group: 'settings-editor', secured: false, order: 4000 }, - setting.ids.ManageTemplates + setting.ids.ManageProjects ) builder.createDoc( setting.class.WorkspaceSettingCategory, diff --git a/models/task/src/index.ts b/models/task/src/index.ts index 57b71a2247..852476f291 100644 --- a/models/task/src/index.ts +++ b/models/task/src/index.ts @@ -15,12 +15,21 @@ import type { Employee, Person } from '@hcengineering/contact' import contact from '@hcengineering/contact' -import { Arr, Attribute, Class, Doc, Domain, IndexKind, Ref, Status, Timestamp } from '@hcengineering/core' +import { + Class, + DOMAIN_MODEL, + Doc, + Domain, + IndexKind, + Ref, + Status, + StatusCategory, + Timestamp +} from '@hcengineering/core' import { Builder, Collection, Hidden, - Implements, Index, Mixin, Model, @@ -34,52 +43,31 @@ import { } from '@hcengineering/model' import attachment from '@hcengineering/model-attachment' import chunter from '@hcengineering/model-chunter' -import core, { TAttachedDoc, TClass, TDoc, TSpace, TStatus } from '@hcengineering/model-core' +import core, { TAttachedDoc, TClass, TDoc, TSpace } from '@hcengineering/model-core' import view, { createAction, template, actionTemplates as viewTemplates } from '@hcengineering/model-view' -import {} from '@hcengineering/notification' import { IntlString } from '@hcengineering/platform' +import { PaletteColorIndexes } from '@hcengineering/ui/src/colors' import tags from '@hcengineering/tags' import { - DoneState, - DoneStateTemplate, KanbanCard, - KanbanTemplate, - KanbanTemplateSpace, - LostState, - LostStateTemplate, + Project, + ProjectStatus, + ProjectType, + ProjectTypeCategory, Sequence, - State, - StateTemplate, Task, - TodoItem, - WonState, - WonStateTemplate + TodoItem } from '@hcengineering/task' -import { AnyComponent } from '@hcengineering/ui' +import type { AnyComponent } from '@hcengineering/ui' import { ViewAction } from '@hcengineering/view' import task from './plugin' export { taskId } from '@hcengineering/task' -export { createKanbanTemplate, createSequence, taskOperation } from './migration' +export { createProjectType, createSequence, taskOperation } from './migration' export { default } from './plugin' export const DOMAIN_TASK = 'task' as Domain export const DOMAIN_KANBAN = 'kanban' as Domain -@Model(task.class.State, core.class.Status) -@UX(task.string.TaskState, task.icon.TaskState, undefined, 'rank', 'name') -export class TState extends TStatus implements State { - isArchived!: boolean -} - -@Model(task.class.DoneState, core.class.Status) -@UX(task.string.TaskStateDone, task.icon.TaskState, undefined, 'name') -export class TDoneState extends TStatus implements DoneState {} - -@Model(task.class.WonState, task.class.DoneState) -export class TWonState extends TDoneState implements WonState {} - -@Model(task.class.LostState, task.class.DoneState) -export class TLostState extends TDoneState implements LostState {} /** * @public @@ -93,10 +81,6 @@ export class TTask extends TAttachedDoc implements Task { @Index(IndexKind.Indexed) status!: Ref - @Prop(TypeRef(task.class.DoneState), task.string.TaskStateDone, { _id: task.attribute.DoneState }) - @Index(IndexKind.Indexed) - doneState!: Ref | null - @Prop(TypeString(), task.string.TaskNumber) @Index(IndexKind.FullText) @Hidden() @@ -118,6 +102,8 @@ export class TTask extends TAttachedDoc implements Task { @Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files }) attachments?: number + + isDone?: boolean } @Model(task.class.TodoItem, core.class.AttachedDoc, DOMAIN_TASK) @@ -147,74 +133,27 @@ export class TKanbanCard extends TClass implements KanbanCard { card!: AnyComponent } -@Model(task.class.SpaceWithStates, core.class.Space) -export class TSpaceWithStates extends TSpace { - templateId!: Ref - states!: Arr> - doneStates!: Arr> +@Model(task.class.Project, core.class.Space) +export class TProject extends TSpace implements Project { + type!: Ref } -@Model(task.class.KanbanTemplateSpace, core.class.Space) -export class TKanbanTemplateSpace extends TSpace implements KanbanTemplateSpace { - declare name: IntlString - declare description: IntlString +@Model(task.class.ProjectType, core.class.Space) +export class TProjectType extends TSpace implements ProjectType { + statuses!: ProjectStatus[] + shortDescription?: string + category!: Ref +} + +@Model(task.class.ProjectTypeCategory, core.class.Doc, DOMAIN_MODEL) +export class TProjectTypeCategory extends TDoc implements ProjectTypeCategory { + name!: IntlString + description!: IntlString icon!: AnyComponent - editor!: AnyComponent - ofAttribute!: Ref> - doneAttribute!: Ref> - attachedToClass!: Ref> -} - -@Model(task.class.StateTemplate, core.class.Doc, DOMAIN_KANBAN) -export class TStateTemplate extends TDoc implements StateTemplate { - // We attach to attribute, so we could distinguish between - ofAttribute!: Ref> - attachedTo!: Ref - - @Prop(TypeString(), task.string.StateTemplateTitle) - name!: string - - @Prop(TypeString(), task.string.StateTemplateColor) - color!: number - - declare rank: string -} - -@Model(task.class.DoneStateTemplate, core.class.Doc, DOMAIN_KANBAN) -export class TDoneStateTemplate extends TDoc implements DoneStateTemplate { - // We attach to attribute, so we could distinguish between - ofAttribute!: Ref> - attachedTo!: Ref - - @Prop(TypeString(), task.string.StateTemplateTitle) - name!: string - - declare rank: string -} - -@Model(task.class.WonStateTemplate, task.class.DoneStateTemplate) -export class TWonStateTemplate extends TDoneStateTemplate implements WonStateTemplate {} - -@Model(task.class.LostStateTemplate, task.class.DoneStateTemplate) -export class TLostStateTemplate extends TDoneStateTemplate implements LostStateTemplate {} - -@Model(task.class.KanbanTemplate, core.class.Doc, DOMAIN_KANBAN) -export class TKanbanTemplate extends TDoc implements KanbanTemplate { - @Prop(TypeString(), task.string.KanbanTemplateTitle) - @Index(IndexKind.FullText) - title!: string - - @Prop(TypeString(), task.string.Description) - description!: string - - @Prop(TypeString(), task.string.ShortDescription) - shortDescription!: string - - @Prop(Collection(task.class.StateTemplate), task.string.States) - statesC!: number - - @Prop(Collection(task.class.DoneStateTemplate), task.string.DoneStates) - doneStatesC!: number + editor?: AnyComponent + attachedToClass!: Ref> + statusClass!: Ref> + statusCategories!: Ref[] } @Model(task.class.Sequence, core.class.Doc, DOMAIN_KANBAN) @@ -223,13 +162,6 @@ export class TSequence extends TDoc implements Sequence { sequence!: number } -@Implements(task.interface.DocWithRank) -export class TDocWithRank extends TDoc { - @Prop(TypeString(), task.string.Rank) - @Hidden() - rank!: string -} - /** * @public */ @@ -287,24 +219,7 @@ export const actionTemplates = template({ }) export function createModel (builder: Builder): void { - builder.createModel( - TDocWithRank, - TState, - TDoneState, - TWonState, - TLostState, - TKanbanCard, - TKanbanTemplateSpace, - TStateTemplate, - TDoneStateTemplate, - TWonStateTemplate, - TLostStateTemplate, - TKanbanTemplate, - TSequence, - TTask, - TTodoItem, - TSpaceWithStates - ) + builder.createModel(TKanbanCard, TSequence, TTask, TTodoItem, TProject, TProjectType, TProjectTypeCategory) builder.createDoc( view.class.ViewletDescriptor, @@ -321,7 +236,7 @@ export function createModel (builder: Builder): void { presenter: view.component.ObjectPresenter }) - builder.mixin(task.class.KanbanTemplate, core.class.Class, view.mixin.ObjectPresenter, { + builder.mixin(task.class.ProjectType, core.class.Class, view.mixin.ObjectPresenter, { presenter: task.component.KanbanTemplatePresenter }) @@ -340,11 +255,7 @@ export function createModel (builder: Builder): void { builder, { ...actionTemplates.editStatus, - target: task.class.SpaceWithStates, - actionProps: { - ofAttribute: task.attribute.State, - doneOfAttribute: task.attribute.DoneState - }, + target: task.class.Project, query: { archived: false }, @@ -356,34 +267,22 @@ export function createModel (builder: Builder): void { task.action.EditStatuses ) - builder.mixin(task.class.State, core.class.Class, view.mixin.AttributeEditor, { + builder.mixin(core.class.Status, core.class.Class, view.mixin.AttributeEditor, { inlineEditor: task.component.StateEditor }) - builder.mixin(task.class.State, core.class.Class, view.mixin.ObjectPresenter, { + builder.mixin(core.class.Status, core.class.Class, view.mixin.ObjectPresenter, { presenter: task.component.StatePresenter }) - builder.mixin(task.class.State, core.class.Class, view.mixin.AttributePresenter, { + builder.mixin(core.class.Status, core.class.Class, view.mixin.AttributePresenter, { presenter: task.component.StateRefPresenter }) - builder.mixin(task.class.State, core.class.Class, view.mixin.IgnoreActions, { + builder.mixin(core.class.Status, core.class.Class, view.mixin.IgnoreActions, { actions: [view.action.Delete] }) - builder.mixin(task.class.DoneState, core.class.Class, view.mixin.AttributeEditor, { - inlineEditor: task.component.DoneStateEditor - }) - - builder.mixin(task.class.DoneState, core.class.Class, view.mixin.ObjectPresenter, { - presenter: task.component.DoneStatePresenter - }) - - builder.mixin(task.class.DoneState, core.class.Class, view.mixin.AttributePresenter, { - presenter: task.component.DoneStateRefPresenter - }) - builder.createDoc( view.class.ViewletDescriptor, core.space.Model, @@ -467,38 +366,53 @@ export function createModel (builder: Builder): void { task.action.Move ) - createAction( - builder, + builder.createDoc( + core.class.StatusCategory, + core.space.Model, { - action: view.actionImpl.UpdateDocument, - actionProps: { - key: 'isArchived', - value: true, - ask: true, - label: task.string.Archive, - message: task.string.ArchiveConfirm - }, - query: { - isArchived: { $nin: [true] } - }, - label: task.string.Archive, - icon: view.icon.Archive, - input: 'any', - category: task.category.Task, - target: task.class.State, - context: { - mode: ['context', 'browser'], - group: 'tools' - } + ofAttribute: task.attribute.State, + label: core.string.Status, + icon: task.icon.TaskState, + color: PaletteColorIndexes.Blueberry, + defaultStatusName: 'New state', + order: 0 }, - task.action.ArchiveState + task.statusCategory.Active ) - builder.mixin(task.class.State, core.class.Class, view.mixin.SortFuncs, { + builder.createDoc( + core.class.StatusCategory, + core.space.Model, + { + ofAttribute: task.attribute.State, + label: task.string.DoneStatesWon, + icon: task.icon.TaskState, + color: PaletteColorIndexes.Houseplant, + defaultStatusName: 'Won', + order: 0 + }, + task.statusCategory.Won + ) + + builder.createDoc( + core.class.StatusCategory, + core.space.Model, + { + ofAttribute: task.attribute.State, + label: task.string.DoneStatesLost, + icon: task.icon.TaskState, + color: PaletteColorIndexes.Firework, + defaultStatusName: 'Lost', + order: 0 + }, + task.statusCategory.Lost + ) + + builder.mixin(core.class.Status, core.class.Class, view.mixin.SortFuncs, { func: task.function.StatusSort }) - builder.mixin(task.class.State, core.class.Class, view.mixin.AllValuesFunc, { + builder.mixin(core.class.Status, core.class.Class, view.mixin.AllValuesFunc, { func: task.function.GetAllStates }) diff --git a/models/task/src/migration.ts b/models/task/src/migration.ts index dabb8fdf9e..281d3a873e 100644 --- a/models/task/src/migration.ts +++ b/models/task/src/migration.ts @@ -18,48 +18,41 @@ import { Class, DOMAIN_STATUS, DOMAIN_TX, + Data, Doc, - Domain, Ref, Space, Status, - Tx, - TxCollectionCUD, - TxCreateDoc, TxOperations, - TxProcessor, - TxUpdateDoc, + generateId, toIdMap } from '@hcengineering/core' -import { MigrateOperation, MigrationClient, MigrationUpgradeClient, createOrUpdate } from '@hcengineering/model' +import { + MigrateOperation, + MigrationClient, + MigrationUpgradeClient, + createOrUpdate, + tryMigrate, + tryUpgrade +} from '@hcengineering/model' import core, { DOMAIN_SPACE } from '@hcengineering/model-core' import tags from '@hcengineering/model-tags' -import { DOMAIN_VIEW } from '@hcengineering/model-view' -import { DoneState, DoneStateTemplate, KanbanTemplate, State, StateTemplate, Task, genRanks } from '@hcengineering/task' -import view, { Filter, FilteredView } from '@hcengineering/view' -import { DOMAIN_TASK } from '.' +import { + Project, + ProjectStatus, + ProjectType, + ProjectTypeCategory, + Task, + createState, + taskId +} from '@hcengineering/task' +import view, { Filter } from '@hcengineering/view' +import { DOMAIN_KANBAN, DOMAIN_TASK } from '.' import task from './plugin' -/** - * @public - */ -export const DOMAIN_STATE = 'state' as Domain - +type ProjectData = Omit, 'statuses' | 'private' | 'members' | 'archived'> type OldStatus = Status & { rank: string } -/** - * @public - */ -export interface KanbanTemplateData { - kanbanId: Ref - space: Ref - title: KanbanTemplate['title'] - description?: string - shortDescription?: string - states: Pick[] - doneStates: (Pick & { isWon: boolean })[] -} - /** * @public */ @@ -75,51 +68,39 @@ export async function createSequence (tx: TxOperations, _class: Ref>) /** * @public */ -export async function createKanbanTemplate ( +export async function createProjectType ( client: TxOperations, - data: KanbanTemplateData, - ofAttribute: Ref>, - doneAtrtribute?: Ref> -): Promise> { - const current = await client.findOne(task.class.KanbanTemplate, { _id: data.kanbanId }) + data: ProjectData, + states: Data[], + _id: Ref, + stateClass: Ref> = core.class.Status +): Promise> { + const current = await client.findOne(task.class.ProjectType, { _id }) if (current !== undefined) { return current._id } + const statuses: Ref[] = [] + for (const st of states) { + statuses.push(await createState(client, stateClass, st)) + } + const tmpl = await client.createDoc( - task.class.KanbanTemplate, - data.space, + task.class.ProjectType, + core.space.Model, { - doneStatesC: 0, - statesC: 0, - title: data.title + description: data.description, + shortDescription: data.shortDescription, + category: data.category, + statuses: statuses.map((p) => { + return { _id: p } + }), + name: data.name, + private: false, + members: [], + archived: false }, - data.kanbanId - ) - - const doneStateRanks = [...genRanks(data.doneStates.length)] - await Promise.all( - data.doneStates.map((st, i) => - client.createDoc(st.isWon ? task.class.WonStateTemplate : task.class.LostStateTemplate, data.space, { - rank: doneStateRanks[i], - ofAttribute: doneAtrtribute ?? ofAttribute, - name: st.name, - attachedTo: data.kanbanId - }) - ) - ) - - const stateRanks = [...genRanks(data.states.length)] - await Promise.all( - data.states.map((st, i) => - client.createDoc(task.class.StateTemplate, data.space, { - attachedTo: data.kanbanId, - ofAttribute, - rank: stateRanks[i], - name: st.name, - color: st.color - }) - ) + _id ) return tmpl @@ -235,30 +216,23 @@ async function renameStatePrefs (client: MigrationUpgradeClient): Promise async function fixStatusAttributes (client: MigrationClient): Promise { const spaces = await client.find(DOMAIN_SPACE, {}) const map = toIdMap(spaces) - const oldStatuses = await client.find(DOMAIN_STATUS, { space: { $ne: task.space.Statuses } }) + const oldStatuses = await client.find(DOMAIN_STATUS, { ofAttribute: { $exists: false } }) for (const oldStatus of oldStatuses) { const space = map.get(oldStatus.space) if (space !== undefined) { try { - const isDone = client.hierarchy.isDerived(oldStatus._class, task.class.DoneState) let ofAttribute = task.attribute.State if (space._class === ('recruit:class:Vacancy' as Ref>)) { - ofAttribute = isDone - ? ('recruit:attribute:DoneState' as Ref>) - : ('recruit:attribute:State' as Ref>) + ofAttribute = 'recruit:attribute:State' as Ref> } if (space._class === ('lead:class:Funnel' as Ref>)) { - ofAttribute = isDone - ? ('lead:attribute:DoneState' as Ref>) - : ('lead:attribute:State' as Ref>) + ofAttribute = 'lead:attribute:State' as Ref> } if (space._class === ('board:class:Board' as Ref>)) { - ofAttribute = isDone - ? ('board:attribute:DoneState' as Ref>) - : ('board:attribute:State' as Ref>) + ofAttribute = 'board:attribute:State' as Ref> } if (space._class === ('tracker:class:Project' as Ref>)) { - ofAttribute = 'tracker:attribute:IssueStatus' as Ref> + ofAttribute = 'tracker:attribute:IssueStatus' as Ref> } if (ofAttribute !== oldStatus.ofAttribute) { await client.update(DOMAIN_STATUS, { _id: oldStatus._id }, { ofAttribute }) @@ -270,173 +244,377 @@ async function fixStatusAttributes (client: MigrationClient): Promise { } } -async function migrateStatuses (client: MigrationClient): Promise { - await fixStatusAttributes(client) - const oldStatuses = await client.find(DOMAIN_STATUS, { space: { $ne: task.space.Statuses } }) - const newStatuses: Map = new Map() - const oldStatusesMap = new Map, Ref>() - oldStatuses.sort((a, b) => a.rank.localeCompare(b.rank)) - for (const oldStatus of oldStatuses) { - const name = oldStatus.name.toLowerCase().trim() - const mapId = `${oldStatus.ofAttribute}_${name}` - const current = newStatuses.get(mapId) - if (current !== undefined) { - oldStatusesMap.set(oldStatus._id, current._id) - if (client.hierarchy.isDerived(oldStatus._class, task.class.DoneState)) { - await client.update(DOMAIN_SPACE, { _id: oldStatus.space }, { $addToSet: { doneStates: current._id } }) - } else { - await client.update(DOMAIN_SPACE, { _id: oldStatus.space }, { $addToSet: { states: current._id } }) - } - } else { - newStatuses.set(mapId, oldStatus) - if (client.hierarchy.isDerived(oldStatus._class, task.class.DoneState)) { - await client.update(DOMAIN_SPACE, { _id: oldStatus.space }, { $addToSet: { doneStates: oldStatus._id } }) - } else { - await client.update(DOMAIN_SPACE, { _id: oldStatus.space }, { $addToSet: { states: oldStatus._id } }) - } - } +function getTemplateOfAttribute (space: Ref): Ref> { + let ofAttribute = task.attribute.State + if (space === ('recruit:space:VacancyTemplates' as Ref)) { + ofAttribute = 'recruit:attribute:State' as Ref> } - if (oldStatusesMap.size > 0) { - const tasks = await client.find(DOMAIN_TASK, {}) - for (const task of tasks) { - const update: any = {} - const newStatus = oldStatusesMap.get(task.status) - if (newStatus !== undefined) { - update.status = newStatus - } - if (task.doneState != null) { - const newDoneStatus = oldStatusesMap.get(task.doneState) - if (newDoneStatus !== undefined) { - update.doneState = newDoneStatus - } - } - if (Object.keys(update).length > 0) { - await client.update(DOMAIN_TASK, { _id: task._id }, update) - } - const txes = await client.find(DOMAIN_TX, { 'tx.objectId': task._id }) - for (const tx of txes) { - const update: any = {} - const ctx = tx as TxCollectionCUD - if (ctx.tx._class === core.class.TxCreateDoc) { - const createTx = ctx.tx as TxCreateDoc - const newStatus = oldStatusesMap.get(createTx.attributes.status) - if (newStatus !== undefined) { - update['tx.attributes.status'] = newStatus - } - if (createTx.attributes.doneState != null) { - const newDoneStatus = oldStatusesMap.get(createTx.attributes.doneState) - if (newDoneStatus !== undefined) { - update['tx.attributes.doneState'] = newDoneStatus - } - } - } else if (ctx.tx._class === core.class.TxUpdateDoc) { - const updateTx = ctx.tx as TxUpdateDoc - if (updateTx.operations.status !== undefined) { - const newStatus = oldStatusesMap.get(updateTx.operations.status) - if (newStatus !== undefined) { - update['tx.operations.status'] = newStatus - } - } - if (updateTx.operations.doneState != null) { - const newDoneStatus = oldStatusesMap.get(updateTx.operations.doneState) - if (newDoneStatus !== undefined) { - update['tx.operations.doneState'] = newDoneStatus - } - } - } - if (Object.keys(update).length > 0) { - await client.update(DOMAIN_TX, { _id: tx._id }, update) - } - } - } - - const descendants = client.hierarchy.getDescendants(task.class.Task) - const filters = await client.find(DOMAIN_VIEW, { - _class: view.class.FilteredView, - filterClass: { $in: descendants } - }) - for (const filter of filters) { - const filters = JSON.parse(filter.filters) as Filter[] - let changed = false - for (const filter of filters) { - if (['status', 'doneStatus'].includes(filter.key.key)) { - for (let index = 0; index < filter.value.length; index++) { - const val = filter.value[index] - const newVal = oldStatusesMap.get(val) - if (newVal !== undefined) { - filter.value[index] = newVal - changed = true - } - } - } - } - if (changed) { - await client.update(DOMAIN_VIEW, { _id: filter._id }, { filters: JSON.stringify(filters) }) - } - } + if (space === ('lead:space:FunnelTemplates' as Ref)) { + ofAttribute = 'lead:attribute:State' as Ref> } - const toRemove = Array.from(oldStatusesMap.keys()) - for (const remove of toRemove) { - await client.delete(DOMAIN_STATUS, remove) + if (space === ('board:space:BoardTemplates' as Ref)) { + ofAttribute = 'board:attribute:State' as Ref> } - await client.update(DOMAIN_STATUS, { rank: { $exists: true } }, { $unset: { rank: '' } }) - await client.update(DOMAIN_STATUS, { space: { $ne: task.space.Statuses } }, { space: task.space.Statuses }) + return ofAttribute } -async function fixFilters (client: MigrationClient): Promise { - const currentStatuses = await client.find(DOMAIN_STATUS, {}) - const currentStatusesMap = toIdMap(currentStatuses) - const cacheMap = new Map, Ref>() - const descendants = client.hierarchy.getDescendants(task.class.Task) - const filters = await client.find(DOMAIN_VIEW, { - _class: view.class.FilteredView, - filterClass: { $in: descendants } +async function fixStatusDoneAttributes (client: MigrationClient): Promise { + const oldStatuses = await client.find(DOMAIN_STATUS, {}) + for (const oldStatus of oldStatuses) { + if (!oldStatus.ofAttribute.includes('DoneState')) continue + const ofAttribute = oldStatus.ofAttribute.replace('DoneState', 'State') + await client.update(DOMAIN_STATUS, { _id: oldStatus._id }, { ofAttribute }) + } +} + +async function removeDoneStatuses (client: MigrationClient): Promise { + const tasks = await client.find(DOMAIN_TASK, { doneState: { $exists: true } }) + for (const task of tasks) { + if ((task as any).doneState != null) { + await client.update(DOMAIN_TASK, { _id: task._id }, { status: (task as any).doneState, isDone: true }) + } + await client.update(DOMAIN_TASK, { _id: task._id }, { $unset: { doneState: '' } }) + } + await client.update( + DOMAIN_TX, + { 'tx.operations.doneState': { $ne: null } }, + { $rename: { 'tx.operations.doneState': 'tx.operations.status' } } + ) + await client.update( + DOMAIN_TX, + { 'tx.attributes.doneState': { $ne: null } }, + { $rename: { 'tx.attributes.doneState': 'tx.attributes.status' } } + ) + await client.update( + DOMAIN_TX, + { 'tx.operations.doneState': { $exists: true } }, + { $unset: { 'tx.operations.doneState': '' } } + ) + await client.update( + DOMAIN_TX, + { 'tx.attributes.doneState': { $exists: true } }, + { $unset: { 'tx.attributes.doneState': '' } } + ) + + // we need join doneStates to states for all projects + const classes = client.hierarchy.getDescendants(task.class.Project) + const projects = await client.find(DOMAIN_SPACE, { _class: { $in: classes }, doneStates: { $exists: true } }) + for (const project of projects) { + await client.update( + DOMAIN_SPACE, + { _id: project._id }, + { states: (project as any).states.concat((project as any).doneStates) } + ) + await client.update(DOMAIN_SPACE, { _id: project._id }, { $unset: { doneStates: '' } }) + } +} + +async function removeStateClass (client: MigrationClient): Promise { + await client.update( + DOMAIN_STATUS, + { _class: 'task:class:State' as Ref> }, + { _class: core.class.Status, category: task.statusCategory.Active } + ) + await client.update(DOMAIN_TX, { objectClass: 'task:class:State' }, { objectClass: core.class.Status }) + await client.update( + DOMAIN_STATUS, + { _class: 'task:class:WonState' as Ref> }, + { _class: core.class.Status, category: task.statusCategory.Won } + ) + await client.update(DOMAIN_TX, { objectClass: 'task:class:WonState' }, { objectClass: core.class.Status }) + await client.update( + DOMAIN_STATUS, + { _class: 'task:class:LostState' as Ref> }, + { _class: core.class.Status, category: task.statusCategory.Lost } + ) + await client.update(DOMAIN_TX, { objectClass: 'task:class:LostState' }, { objectClass: core.class.Status }) + await client.update( + DOMAIN_STATUS, + { _class: 'task:class:DoneState' as Ref> }, + { _class: core.class.Status } + ) + await client.update(DOMAIN_TX, { objectClass: 'task:class:DoneState' }, { objectClass: core.class.Status }) +} + +async function migrateTemplatesToTypes (client: MigrationClient): Promise { + interface KanbanTemplate extends Doc { + title: string + description?: string + shortDescription?: string + } + + interface KanbanTemplateSpace extends Space { + attachedToClass: Ref> + } + + interface StateTemplate extends Doc, Status { + attachedTo: Ref + rank: string + } + + const classes = client.hierarchy.getDescendants(task.class.Project) + const templates = await client.find(DOMAIN_KANBAN, { + _class: 'task:class:KanbanTemplate' as Ref> }) - for (const filter of filters) { - const filters = JSON.parse(filter.filters) as Filter[] - let changed = false - for (const filter of filters) { - if (['status', 'doneStatus'].includes(filter.key.key)) { - for (let index = 0; index < filter.value.length; index++) { - const val = filter.value[index] - if (!currentStatusesMap.has(val)) { - const newVal = cacheMap.get(val) - if (newVal !== undefined) { - filter.value[index] = newVal - changed = true - } else { - const ownTxes = await client.find(DOMAIN_TX, { objectId: val }) - const attachedTxes = await client.find(DOMAIN_TX, { 'tx.objectId': val }) - const txes = [...ownTxes, ...attachedTxes].sort((a, b) => a.modifiedOn - b.modifiedOn) - const oldStatus = TxProcessor.buildDoc2Doc(txes) - if (oldStatus !== undefined) { - const newStatus = currentStatuses.find( - (p) => - p.ofAttribute === oldStatus.ofAttribute && - p.name.toLowerCase().trim() === oldStatus.name.toLowerCase().trim() - ) - if (newStatus !== undefined) { - filter.value[index] = newStatus._id - cacheMap.set(val, newStatus._id) - changed = true - } - } - } - } - } + for (const template of templates) { + const used = await client.find(DOMAIN_SPACE, { templateId: template._id }) + if (used.length === 0) { + await client.delete(DOMAIN_KANBAN, template._id) + continue + } + const space = ( + await client.find(DOMAIN_SPACE, { _id: template.space as Ref }) + )[0] + if (space === undefined) continue + const states = await client.find( + DOMAIN_KANBAN, + { _class: 'task:class:StateTemplate' as Ref>, attachedTo: template._id }, + { sort: { rank: 1 } } + ) + const wonStates = await client.find( + DOMAIN_KANBAN, + { _class: 'task:class:WonStateTemplate' as Ref>, attachedTo: template._id }, + { sort: { rank: 1 } } + ) + const lostStates = await client.find( + DOMAIN_KANBAN, + { _class: 'task:class:LostStateTemplate' as Ref>, attachedTo: template._id }, + { sort: { rank: 1 } } + ) + + const statuses: ProjectStatus[] = [] + const currentStates = await client.find(DOMAIN_STATUS, {}) + for (const st of states) { + const exists = currentStates.find((p) => p.name.toLocaleLowerCase() === st.name.toLocaleLowerCase()) + if (exists !== undefined) { + statuses.push({ _id: exists._id, color: st.color }) + } else { + const id = generateId() + await client.create(DOMAIN_STATUS, { + ofAttribute: getTemplateOfAttribute(st.space), + name: st.name, + _id: id, + space: task.space.Statuses, + modifiedOn: st.modifiedOn, + modifiedBy: st.modifiedBy, + _class: core.class.Status, + color: st.color ?? 9, + createdBy: st.createdBy, + createdOn: st.createdOn, + category: task.statusCategory.Active + }) + statuses.push({ _id: id, color: st.color }) } } - if (changed) { - await client.update(DOMAIN_VIEW, { _id: filter._id }, { filters: JSON.stringify(filters) }) + + for (const st of wonStates) { + const exists = currentStates.find((p) => p.name.toLocaleLowerCase() === st.name.toLocaleLowerCase()) + if (exists !== undefined) { + statuses.push({ _id: exists._id, color: st.color }) + } else { + const id = generateId() + await client.create(DOMAIN_STATUS, { + ofAttribute: getTemplateOfAttribute(st.space), + name: st.name, + _id: id, + space: task.space.Statuses, + modifiedOn: st.modifiedOn, + modifiedBy: st.modifiedBy, + _class: core.class.Status, + color: st.color ?? 15, + createdBy: st.createdBy, + createdOn: st.createdOn, + category: task.statusCategory.Won + }) + statuses.push({ _id: id, color: st.color }) + } + } + + for (const st of lostStates) { + const exists = currentStates.find((p) => p.name.toLocaleLowerCase() === st.name.toLocaleLowerCase()) + if (exists !== undefined) { + statuses.push({ _id: exists._id, color: st.color }) + } else { + const id = generateId() + await client.create(DOMAIN_STATUS, { + ofAttribute: getTemplateOfAttribute(st.space), + name: st.name, + _id: id, + space: task.space.Statuses, + modifiedOn: st.modifiedOn, + modifiedBy: st.modifiedBy, + _class: core.class.Status, + color: st.color ?? 0, + createdBy: st.createdBy, + createdOn: st.createdOn, + category: task.statusCategory.Lost + }) + statuses.push({ _id: id, color: st.color }) + } + } + + const category = await getProjectTypeCategory(client, space.attachedToClass) + await client.create(DOMAIN_SPACE, { + name: template.title, + description: template.description ?? '', + shortDescription: template.shortDescription, + category: category ?? (template.space as Ref as Ref), + private: false, + members: [], + archived: false, + _id: template._id as Ref as Ref, + space: core.space.Space, + modifiedOn: template.modifiedOn, + modifiedBy: template.modifiedBy, + _class: task.class.ProjectType, + statuses + }) + await client.delete(DOMAIN_KANBAN, template._id) + } + + // we should found all projects without types and has templateID we should just rename it to type (if it exists) + const projectsWithTemplate = await client.find(DOMAIN_SPACE, { + _class: { $in: classes }, + type: { $exists: false }, + templateId: { $exists: true } + }) + for (const project of projectsWithTemplate) { + await client.update(DOMAIN_SPACE, { _id: project._id }, { type: (project as any).templateId }) + } + + // we should remove all state templates + const stateClasses = [ + 'task:class:StateTemplate' as Ref>, + 'task:class:WonStateTemplate' as Ref>, + 'task:class:LostStateTemplate' as Ref> + ] + const states = await client.find(DOMAIN_KANBAN, { _class: { $in: stateClasses } }) + for (const st of states) { + await client.delete(DOMAIN_KANBAN, st._id) + } +} + +async function getProjectTypeCategory ( + client: MigrationClient, + _class: Ref> +): Promise | undefined> { + let clazz = _class + while (true) { + const res = await client.model.findAll(task.class.ProjectTypeCategory, { attachedToClass: clazz }) + if (res[0] !== undefined) return res[0]._id + const parent = client.hierarchy.getClass(clazz) + if (parent.extends === undefined) return + clazz = parent.extends + } +} + +async function migrateProjectTypes (client: MigrationClient): Promise { + const classes = client.hierarchy.getDescendants(task.class.Project) + // we should found all projects without types and group by class and then by statuses and create new type + const projects = await client.find(DOMAIN_SPACE, { _class: { $in: classes }, type: { $exists: false } }) + const projectsByCategory: Map, Project[]> = new Map() + for (const project of projects) { + const category = await getProjectTypeCategory(client, project._class) + if (category === undefined) continue + const arr = projectsByCategory.get(category) ?? [] + arr.push(project) + projectsByCategory.set(category, arr) + } + for (const [category, projects] of projectsByCategory) { + const gouped = group(projects) + for (const gr of gouped) { + await client.create(DOMAIN_SPACE, { + category, + name: gr.projects[0].name, + description: '', + private: false, + members: [], + archived: false, + _id: gr.type, + space: core.space.Space, + modifiedOn: Date.now(), + modifiedBy: core.account.System, + _class: task.class.ProjectType, + statuses: gr.statuses.map((p) => { + return { _id: p } + }) + }) + + await client.update(DOMAIN_SPACE, { _id: { $in: gr.projects.map((p) => p._id) } }, { type: gr.type }) } } + + // we need remove states from all projects + const allProjects = await client.find(DOMAIN_SPACE, { _class: { $in: classes } }) + await Promise.all( + allProjects.map(async (project) => { + await client.update( + DOMAIN_SPACE, + { _id: project._id }, + { $unset: { states: '', templateId: '', doneStates: '' } } + ) + }) + ) +} + +interface ProjectTypeGroup { + statuses: Ref[] + projects: Project[] + type: Ref +} + +function group (projects: Project[]): ProjectTypeGroup[] { + const map: Map = new Map() + + for (const project of projects) { + const ids = getIds((project as any).states ?? []) + const obj: ProjectTypeGroup = map.get(ids) ?? { + statuses: (project as any).states ?? [], + projects: [], + type: generateId() + } + obj.projects.push(project) + map.set(ids, obj) + } + return Array.from(map.values()) +} + +function getIds (states: Ref[]): string { + return states.join(',') } export const taskOperation: MigrateOperation = { async migrate (client: MigrationClient): Promise { - await renameState(client) - await migrateStatuses(client) - await fixFilters(client) + await tryMigrate(client, taskId, [ + { + state: 'fixStatusAttributes', + func: fixStatusAttributes + }, + { + state: 'renameState', + func: renameState + }, + { + state: 'removeDoneStatuses', + func: removeDoneStatuses + }, + { + state: 'fixStatusDoneAttributes', + func: fixStatusDoneAttributes + }, + { + state: 'removeStateClass', + func: removeStateClass + }, + { + state: 'migrateTemplatesToTypes', + func: migrateTemplatesToTypes + }, + { + state: 'migrateProjectTypes', + func: migrateProjectTypes + } + ]) }, async upgrade (client: MigrationUpgradeClient): Promise { const tx = new TxOperations(client, core.account.System) @@ -455,6 +633,12 @@ export const taskOperation: MigrateOperation = { }, task.category.TaskTag ) - await renameStatePrefs(client) + + await tryUpgrade(client, taskId, [ + { + state: 'renameStatePrefs', + func: renameStatePrefs + } + ]) } } diff --git a/models/task/src/plugin.ts b/models/task/src/plugin.ts index 48a2c15e9d..16e3a0408b 100644 --- a/models/task/src/plugin.ts +++ b/models/task/src/plugin.ts @@ -21,6 +21,7 @@ import { taskId } from '@hcengineering/task' import task from '@hcengineering/task-resources/src/plugin' import type { AnyComponent } from '@hcengineering/ui' import type { Action, ActionCategory, ViewAction, Viewlet } from '@hcengineering/view' +import {} from '@hcengineering/notification' export default mergeIds(taskId, task, { action: { @@ -48,9 +49,7 @@ export default mergeIds(taskId, task, { KanbanTemplatePresenter: '' as AnyComponent, KanbanCard: '' as AnyComponent, StatePresenter: '' as AnyComponent, - DoneStatePresenter: '' as AnyComponent, StateEditor: '' as AnyComponent, - DoneStateEditor: '' as AnyComponent, KanbanView: '' as AnyComponent, Todos: '' as AnyComponent, TodoItemPresenter: '' as AnyComponent, @@ -58,8 +57,9 @@ export default mergeIds(taskId, task, { TaskHeader: '' as AnyComponent, Dashboard: '' as AnyComponent, StateRefPresenter: '' as AnyComponent, - DoneStateRefPresenter: '' as AnyComponent, - StatusSelector: '' as AnyComponent + StatusSelector: '' as AnyComponent, + TemplatesIcon: '' as AnyComponent, + TypesView: '' as AnyComponent }, space: { TasksPublic: '' as Ref diff --git a/models/tracker/src/index.ts b/models/tracker/src/index.ts index 41b01eb4b8..f78657c66a 100644 --- a/models/tracker/src/index.ts +++ b/models/tracker/src/index.ts @@ -520,4 +520,24 @@ export function createModel (builder: Builder): void { secured: false, order: 4000 }) + + builder.createDoc( + task.class.ProjectTypeCategory, + core.space.Model, + { + name: tracker.string.Projects, + description: tracker.string.ManageWorkflowStatuses, + icon: task.component.TemplatesIcon, + attachedToClass: tracker.class.Project, + statusClass: tracker.class.IssueStatus, + statusCategories: [ + tracker.issueStatusCategory.Backlog, + tracker.issueStatusCategory.Unstarted, + tracker.issueStatusCategory.Started, + tracker.issueStatusCategory.Completed, + tracker.issueStatusCategory.Canceled + ] + }, + tracker.category.ProjectTypeCategory + ) } diff --git a/models/tracker/src/migration.ts b/models/tracker/src/migration.ts index 06730b11b1..1812b4316b 100644 --- a/models/tracker/src/migration.ts +++ b/models/tracker/src/migration.ts @@ -14,14 +14,13 @@ // import core, { - DOMAIN_STATUS, DOMAIN_TX, + Data, + SortingOrder, Status, - TxCUD, TxCollectionCUD, TxCreateDoc, TxOperations, - TxProcessor, TxUpdateDoc } from '@hcengineering/core' import { @@ -31,13 +30,12 @@ import { createOrUpdate, tryMigrate } from '@hcengineering/model' -import { DOMAIN_TASK } from '@hcengineering/model-task' +import { DOMAIN_TASK, createProjectType } from '@hcengineering/model-task' import tags from '@hcengineering/tags' -import { Issue, Project, TimeReportDayType, TimeSpendReport, createStatuses } from '@hcengineering/tracker' -import { DOMAIN_TRACKER } from './types' -import tracker from './plugin' -import { DOMAIN_SPACE } from '@hcengineering/model-core' +import { Issue, TimeReportDayType, TimeSpendReport } from '@hcengineering/tracker' import view from '@hcengineering/view' +import tracker from './plugin' +import { DOMAIN_TRACKER } from './types' async function createDefaultProject (tx: TxOperations): Promise { const current = await tx.findOne(tracker.class.Project, { @@ -50,26 +48,59 @@ async function createDefaultProject (tx: TxOperations): Promise { // Create new if not deleted by customers. if (current === undefined && currentDeleted === undefined) { - const states = await createStatuses(tx, tracker.class.IssueStatus, tracker.attribute.IssueStatus) - - await tx.createDoc( - tracker.class.Project, - core.space.Space, - { - name: 'Default', - description: 'Default project', - private: false, - members: [], - archived: false, - identifier: 'TSK', - sequence: 0, - defaultIssueStatus: states[0], - defaultTimeReportDay: TimeReportDayType.PreviousWorkDay, - defaultAssignee: undefined, - states - }, - tracker.project.DefaultProject + const categories = await tx.findAll( + core.class.StatusCategory, + { ofAttribute: tracker.attribute.IssueStatus }, + { sort: { order: SortingOrder.Ascending } } ) + + const states: Omit, 'rank'>[] = [] + + for (const category of categories) { + states.push({ + ofAttribute: tracker.attribute.IssueStatus, + name: category.defaultStatusName, + category: category._id + }) + } + + const typeId = await createProjectType( + tx, + { + name: 'Base project', + category: tracker.category.ProjectTypeCategory, + description: '' + }, + states, + tracker.ids.BaseProjectType, + tracker.class.IssueStatus + ) + + const state = await tx.findOne( + tracker.class.IssueStatus, + { space: typeId }, + { sort: { rank: SortingOrder.Ascending } } + ) + if (state !== undefined) { + await tx.createDoc( + tracker.class.Project, + core.space.Space, + { + name: 'Default', + description: 'Default project', + private: false, + members: [], + archived: false, + identifier: 'TSK', + sequence: 0, + defaultIssueStatus: state._id, + defaultTimeReportDay: TimeReportDayType.PreviousWorkDay, + defaultAssignee: undefined, + type: typeId + }, + tracker.project.DefaultProject + ) + } } } @@ -173,7 +204,11 @@ async function fixEstimation (client: MigrationClient): Promise { async function fixRemainingTime (client: MigrationClient): Promise { while (true) { - const issues = await client.find(DOMAIN_TASK, { remainingTime: { $exists: false } }, { limit: 1000 }) + const issues = await client.find( + DOMAIN_TASK, + { _class: tracker.class.Issue, remainingTime: { $exists: false } }, + { limit: 1000 } + ) for (const issue of issues) { await client.update( DOMAIN_TASK, @@ -185,6 +220,11 @@ async function fixRemainingTime (client: MigrationClient): Promise { break } } + await client.update( + DOMAIN_TASK, + { _class: { $ne: tracker.class.Issue }, remainingTime: { $exists: true } }, + { $unset: { remainingTime: '' } } + ) } async function moveIssues (client: MigrationClient): Promise { @@ -194,28 +234,6 @@ async function moveIssues (client: MigrationClient): Promise { } } -async function fixProjectDefaultStatuses (client: MigrationClient): Promise { - const projects = await client.find(DOMAIN_SPACE, { _class: tracker.class.Project }) - for (const project of projects) { - const state = await client.find(DOMAIN_STATUS, { _id: project.defaultIssueStatus }) - if (state.length === 0) { - const oldStateTxes = await client.find>(DOMAIN_TX, { objectId: project.defaultIssueStatus }) - const oldState = TxProcessor.buildDoc2Doc(oldStateTxes) - if (oldState !== undefined) { - const newState = await client.find(DOMAIN_STATUS, { - name: oldState.name.trim(), - ofAttribute: tracker.attribute.IssueStatus - }) - if (newState.length > 0) { - await client.update(DOMAIN_SPACE, { _id: project._id }, { defaultIssueStatus: newState[0]._id }) - } - } else { - await client.update(DOMAIN_SPACE, { _id: project._id }, { defaultIssueStatus: project.states[0] }) - } - } - } -} - export const trackerOperation: MigrateOperation = { async migrate (client: MigrationClient): Promise { await tryMigrate(client, 'tracker', [ @@ -223,10 +241,6 @@ export const trackerOperation: MigrateOperation = { state: 'moveIssues', func: moveIssues }, - { - state: 'fixProjectDefaultStatuses', - func: fixProjectDefaultStatuses - }, { state: 'reportTimeDayToHour', func: fixSpentTime diff --git a/models/tracker/src/plugin.ts b/models/tracker/src/plugin.ts index 0ebe5644d6..f74f0e673c 100644 --- a/models/tracker/src/plugin.ts +++ b/models/tracker/src/plugin.ts @@ -14,16 +14,17 @@ // limitations under the License. // +import { TxViewlet } from '@hcengineering/activity' import { Doc, Ref } from '@hcengineering/core' import { ObjectSearchCategory, ObjectSearchFactory } from '@hcengineering/model-presentation' -import { IntlString, mergeIds, Resource } from '@hcengineering/platform' +import { NotificationGroup, NotificationType } from '@hcengineering/notification' +import { IntlString, Resource, mergeIds } from '@hcengineering/platform' +import { ProjectType } from '@hcengineering/task' import { trackerId } from '@hcengineering/tracker' import tracker from '@hcengineering/tracker-resources/src/plugin' import type { AnyComponent } from '@hcengineering/ui/src/types' import { Action, ViewAction, Viewlet } from '@hcengineering/view' import { Application } from '@hcengineering/workbench' -import { TxViewlet } from '@hcengineering/activity' -import { NotificationGroup, NotificationType } from '@hcengineering/notification' export default mergeIds(trackerId, tracker, { string: { @@ -73,7 +74,8 @@ export default mergeIds(trackerId, tracker, { ids: { TxIssueCreated: '' as Ref, TrackerNotificationGroup: '' as Ref, - AssigneeNotification: '' as Ref + AssigneeNotification: '' as Ref, + BaseProjectType: '' as Ref }, completion: { IssueQuery: '' as Resource, diff --git a/models/tracker/src/types.ts b/models/tracker/src/types.ts index f2e6077fe7..4b881b815c 100644 --- a/models/tracker/src/types.ts +++ b/models/tracker/src/types.ts @@ -43,10 +43,9 @@ import { import attachment from '@hcengineering/model-attachment' import chunter from '@hcengineering/model-chunter' import core, { TAttachedDoc, TDoc, TStatus, TType } from '@hcengineering/model-core' -import task, { TSpaceWithStates, TTask } from '@hcengineering/model-task' +import task, { TTask, TProject as TTaskProject } from '@hcengineering/model-task' import { IntlString } from '@hcengineering/platform' import tags, { TagElement } from '@hcengineering/tags' -import { DoneState } from '@hcengineering/task' import { Component, Issue, @@ -68,9 +67,6 @@ import { import tracker from './plugin' export const DOMAIN_TRACKER = 'tracker' as Domain -/** - * @public - */ @Model(tracker.class.IssueStatus, core.class.Status) @UX(tracker.string.IssueStatuses, undefined, undefined, 'rank', 'name') @@ -105,9 +101,9 @@ export class TTypeMilestoneStatus extends TType {} * @public */ -@Model(tracker.class.Project, task.class.SpaceWithStates) +@Model(tracker.class.Project, task.class.Project) @UX(tracker.string.Project, tracker.icon.Issues, 'Project', 'name') -export class TProject extends TSpaceWithStates implements Project { +export class TProject extends TTaskProject implements Project { @Prop(TypeString(), tracker.string.ProjectIdentifier) @Index(IndexKind.FullText) identifier!: IntlString @@ -219,10 +215,6 @@ export class TIssue extends TTask implements Issue { @Prop(Collection(tags.class.TagReference), tracker.string.Labels) declare labels: number - @Prop(TypeRef(task.class.DoneState), task.string.TaskStateDone, { _id: task.attribute.DoneState }) - @Hidden() - declare doneState: Ref | null - @Prop(TypeRef(tracker.class.Project), tracker.string.Project, { icon: tracker.icon.Issues }) @Index(IndexKind.Indexed) @ReadOnly() diff --git a/models/view/src/migration.ts b/models/view/src/migration.ts index 8bb162f702..3086b035ea 100644 --- a/models/view/src/migration.ts +++ b/models/view/src/migration.ts @@ -13,9 +13,76 @@ // limitations under the License. // -import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model' +import { MigrateOperation, MigrationClient, MigrationUpgradeClient, tryMigrate } from '@hcengineering/model' +import { DOMAIN_PREFERENCE } from '@hcengineering/preference' +import view, { Filter, FilteredView, ViewletPreference, viewId } from '@hcengineering/view' +import { DOMAIN_VIEW } from '.' + +async function removeDoneStatePref (client: MigrationClient): Promise { + const prefs = await client.find(DOMAIN_PREFERENCE, { + _class: view.class.ViewletPreference, + config: 'doneState' + }) + for (const pref of prefs) { + await client.update(DOMAIN_PREFERENCE, { _id: pref._id }, { config: pref.config.filter((p) => p !== 'doneState') }) + } + const lookupPrefs = await client.find(DOMAIN_PREFERENCE, { + _class: view.class.ViewletPreference, + config: '$lookup.doneState' + }) + for (const pref of lookupPrefs) { + await client.update( + DOMAIN_PREFERENCE, + { _id: pref._id }, + { config: pref.config.filter((p) => p !== '$lookup.doneState') } + ) + } +} + +async function removeDoneStateFilter (client: MigrationClient): Promise { + const filters = await client.find(DOMAIN_VIEW, { + _class: view.class.FilteredView + }) + for (const filter of filters) { + let changed = false + const options = filter.viewOptions + if (options !== undefined) { + if (options.orderBy[0] === 'doneState') { + options.orderBy[0] = 'status' + changed = true + } + if (options.groupBy.includes('doneState')) { + changed = true + options.groupBy = options.groupBy.filter((g) => g !== 'doneState') + if (options.groupBy.length === 0) { + options.groupBy[0] = 'status' + } + } + } + let filters = JSON.parse(filter.filters) as Filter[] + const initLength = filters.length + filters = filters.filter((p) => p.key.key !== 'doneState') + if (initLength !== filters.length) { + changed = true + } + if (changed) { + await client.update(DOMAIN_VIEW, { _id: filter._id }, { viewOptions: options, filters: JSON.stringify(filters) }) + } + } +} export const viewOperation: MigrateOperation = { - async migrate (client: MigrationClient): Promise {}, + async migrate (client: MigrationClient): Promise { + await tryMigrate(client, viewId, [ + { + state: 'remove-done-state-pref', + func: removeDoneStatePref + }, + { + state: 'remove-done-state-filter', + func: removeDoneStateFilter + } + ]) + }, async upgrade (client: MigrationUpgradeClient): Promise {} } diff --git a/packages/core/src/status.ts b/packages/core/src/status.ts index a9107643a9..2cb49793bf 100644 --- a/packages/core/src/status.ts +++ b/packages/core/src/status.ts @@ -15,7 +15,6 @@ import { Asset, IntlString } from '@hcengineering/platform' import { Attribute, Doc, Domain, Ref } from './classes' -import { AggregateValue, AggregateValueData } from './utils' /** * @public @@ -41,7 +40,6 @@ export const DOMAIN_STATUS = 'status' as Domain export interface Status extends Doc { // We attach to attribute, so we could distinguish between ofAttribute: Ref> - // Optional category. category?: Ref @@ -53,16 +51,3 @@ export interface Status extends Doc { // Optional description description?: string } - -/** - * @public - */ -export class StatusValue extends AggregateValue { - constructor ( - readonly name: string | undefined, - readonly color: number | undefined, - readonly values: AggregateValueData[] - ) { - super(name, values) - } -} diff --git a/packages/kanban/src/components/Kanban.svelte b/packages/kanban/src/components/Kanban.svelte index 01470c5d8c..a79a6b2b46 100644 --- a/packages/kanban/src/components/Kanban.svelte +++ b/packages/kanban/src/components/Kanban.svelte @@ -318,7 +318,9 @@ }} > {#if $$slots.header !== undefined} - + {#key si} + + {/key} {/if} diff --git a/packages/ui/src/components/DropdownLabels.svelte b/packages/ui/src/components/DropdownLabels.svelte index 5fe21f9829..15c47f3350 100644 --- a/packages/ui/src/components/DropdownLabels.svelte +++ b/packages/ui/src/components/DropdownLabels.svelte @@ -40,6 +40,7 @@ export let autoSelect: boolean = true export let useFlexGrow = false export let minW0 = true + export let disabled: boolean = false let container: HTMLElement let opened: boolean = false @@ -61,6 +62,7 @@ {size} {kind} {justify} + {disabled} showTooltip={{ label, direction: labelDirection }} on:click={() => { if (!opened) { diff --git a/packages/ui/src/components/TabList.svelte b/packages/ui/src/components/TabList.svelte index 2841a7d919..d21d9a59f8 100644 --- a/packages/ui/src/components/TabList.svelte +++ b/packages/ui/src/components/TabList.svelte @@ -19,7 +19,7 @@ import Icon from './Icon.svelte' import Label from './Label.svelte' import DropdownLabelsIntl from './DropdownLabelsIntl.svelte' - import { checkAdaptiveMatching, deviceOptionsStore as deviceInfo } from '..' + import { Scroller, checkAdaptiveMatching, deviceOptionsStore as deviceInfo } from '..' export let selected: string | string[] = '' export let multiselect: boolean = false @@ -66,49 +66,51 @@ }} /> {:else} -
- {#each items as item, i} - -
{ - if (multiselect) { - if (Array.isArray(selected)) { - if (selected.includes(item.id)) selected = selected.filter((it) => it !== item.id) - else selected.push(item.id) - } - } else selected = item.id - dispatch('select', item) - items = items - }} - > - {#if item.icon} -
- -
- {:else if item.color} -
- {/if} - {#if item.label || item.labelIntl} - - {#if item.label} - {item.label} - {:else if item.labelIntl} - - {/if} -
- {/each} -
+ +
+ {#each items as item, i} + +
{ + if (multiselect) { + if (Array.isArray(selected)) { + if (selected.includes(item.id)) selected = selected.filter((it) => it !== item.id) + else selected.push(item.id) + } + } else selected = item.id + dispatch('select', item) + items = items + }} + > + {#if item.icon} +
+ +
+ {:else if item.color} +
+ {/if} + {#if item.label || item.labelIntl} + + {#if item.label} + {item.label} + {:else if item.labelIntl} + + {/if} +
+ {/each} +
+ {/if} {/if} diff --git a/plugins/bitrix-resources/src/components/mappings/CreateHRApplicationMapping.svelte b/plugins/bitrix-resources/src/components/mappings/CreateHRApplicationMapping.svelte index a0477303fe..e0392a8eb5 100644 --- a/plugins/bitrix-resources/src/components/mappings/CreateHRApplicationMapping.svelte +++ b/plugins/bitrix-resources/src/components/mappings/CreateHRApplicationMapping.svelte @@ -7,12 +7,12 @@ MappingOperation, getAllAttributes } from '@hcengineering/bitrix' - import { AnyAttribute } from '@hcengineering/core' + import { AnyAttribute, Status } from '@hcengineering/core' import { getEmbeddedLabel } from '@hcengineering/platform' - import { createQuery, getClient } from '@hcengineering/presentation' + import { getClient } from '@hcengineering/presentation' import InlineAttributeBarEditor from '@hcengineering/presentation/src/components/InlineAttributeBarEditor.svelte' import recruit from '@hcengineering/recruit' - import task, { DoneStateTemplate, StateTemplate } from '@hcengineering/task' + import task from '@hcengineering/task' import { Button, DropdownIntlItem, @@ -83,22 +83,9 @@ (it) => ({ id: it.VALUE, label: it.VALUE } as DropdownTextItem) ) - const statusQuery = createQuery() - const doneQuery = createQuery() + const states: Status[] = [] - let stateTemplates: StateTemplate[] = [] - let doneStateTemplates: DoneStateTemplate[] = [] - - $: statusQuery.query(task.class.StateTemplate, { attachedTo: defaultTemplate }, (res) => { - stateTemplates = res - }) - - $: doneQuery.query(task.class.DoneStateTemplate, { attachedTo: defaultTemplate }, (res) => { - doneStateTemplates = res - }) - - $: stateTitles = [{ id: '', label: 'None' }, ...stateTemplates.map((it) => ({ id: it.name, label: it.name }))] - $: doneStateTitles = [{ id: '', label: 'None' }, ...doneStateTemplates.map((it) => ({ id: it.name, label: it.name }))] + $: stateTitles = [{ id: '', label: 'None' }, ...states.map((it) => ({ id: it.name, label: it.name }))]
@@ -123,8 +110,8 @@ width={'10rem'} label={getEmbeddedLabel('Template')} searchField={'title'} - _class={task.class.KanbanTemplate} - docQuery={{ space: recruit.space.VacancyTemplates }} + _class={task.class.ProjectType} + docQuery={{ category: recruit.category.VacancyTypeCategories }} bind:value={defaultTemplate} />
@@ -166,7 +153,7 @@ icon={IconAdd} size={'small'} on:click={() => { - stateMapping = [...stateMapping, { sourceName: '', targetName: '', updateCandidate: [], doneState: '' }] + stateMapping = [...stateMapping, { sourceName: '', targetName: '', updateCandidate: [] }] }} />
@@ -187,14 +174,6 @@ items={stateTitles} bind:selected={m.targetName} /> - Done state: - {#each m.updateCandidate as c} {@const attribute = allAttrs.find((it) => it.name === c.attr)} , + typeId: Ref, account: Ref, company?: Ref ): Promise> { 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 type = await client.findOne(task.class.ProjectType, { _id: typeId }) + if (type === undefined) { + throw Error(`Failed to find target project type: ${typeId}`) } const sequence = await client.findOne(task.class.Sequence, { attachedTo: recruit.class.Vacancy }) @@ -23,24 +23,16 @@ export async function createVacancy ( const incResult = await client.update(sequence, { $inc: { sequence: 1 } }, true) - const [states, doneStates] = await createStates( - client, - recruit.attribute.State, - recruit.attribute.DoneState, - templateId - ) - const id = await client.createDoc(recruit.class.Vacancy, core.space.Space, { name, - description: template.shortDescription ?? '', - fullDescription: template.description, + description: type.shortDescription ?? '', + fullDescription: type.description, private: false, archived: false, company, number: (incResult as any).object.sequence, members: [], - states, - doneStates + type: typeId }) return id @@ -48,7 +40,7 @@ export async function createVacancy ( export async function createApplication ( client: TxOperations, - selectedState: State, + selectedState: Status, _space: Ref, doc: Doc, data: Data @@ -56,10 +48,6 @@ export async function createApplication ( 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') @@ -70,7 +58,7 @@ export async function createApplication ( await client.addCollection(recruit.class.Applicant, _space, doc._id, recruit.mixin.Candidate, 'applications', { ...data, - status: state._id, + status: selectedState._id, number: (incResult as any).object.sequence, rank: calcRank(lastOne, undefined) }) diff --git a/plugins/bitrix/src/types.ts b/plugins/bitrix/src/types.ts index 68e41e88f6..c29a44a9c0 100644 --- a/plugins/bitrix/src/types.ts +++ b/plugins/bitrix/src/types.ts @@ -1,7 +1,7 @@ import { ChannelProvider } from '@hcengineering/contact' import { AnyAttribute, AttachedDoc, Class, Doc, Mixin, Ref } from '@hcengineering/core' import { ExpertKnowledge, InitialKnowledge, MeaningfullKnowledge } from '@hcengineering/tags' -import { KanbanTemplate } from '@hcengineering/task' +import { ProjectType } from '@hcengineering/task' /** * @public @@ -278,8 +278,6 @@ export interface BitrixStateMapping { sourceName: string targetName: string // if empty will not create application - doneState: string // Alternative is to set doneState to value - // Allow to put some values, in case of some statues updateCandidate: { attr: string, value: any }[] } @@ -293,7 +291,7 @@ export interface CreateHRApplication { vacancyField: string // Name of vacancy in bitrix. stateField: string // Name of status in bitrix. - defaultTemplate: Ref + defaultTemplate: Ref copyTalentFields?: { candidate: Ref, applicant: Ref }[] diff --git a/plugins/bitrix/src/utils.ts b/plugins/bitrix/src/utils.ts index 91e11c72ba..f9429fa638 100644 --- a/plugins/bitrix/src/utils.ts +++ b/plugins/bitrix/src/utils.ts @@ -415,6 +415,11 @@ export async function convert ( return } + const type = await client.findOne(task.class.ProjectType, { _id: operation.defaultTemplate }) + if (type === undefined) { + return + } + const vacancies = (extraDocs.get(recruit.class.Vacancy) ?? []) as Vacancy[] const applications = (extraDocs.get(recruit.class.Applicant) ?? []) as Applicant[] @@ -501,21 +506,9 @@ export async function convert ( } } - const states = await client.findAll(task.class.State, { space: vacancyId }) + const states = await client.findAll(core.class.Status, { _id: { $in: type.statuses.map((p) => p._id) } }) const state = states.find((it) => it.name.toLowerCase().trim() === statusName.toLowerCase().trim()) if (state !== undefined) { - if (mapping?.doneState !== '') { - const doneStates = await client.findAll(task.class.DoneState, { space: vacancyId }) - const doneState = doneStates.find( - (it) => it.name.toLowerCase().trim() === mapping?.doneState.toLowerCase().trim() - ) - if (doneState !== undefined) { - if (doneState !== undefined && existing?.doneState !== doneState._id) { - update.doneState = doneState._id - } - } - } - if (existing !== undefined) { if (existing.status !== state?._id) { update.status = state._id diff --git a/plugins/board-resources/src/components/CreateBoard.svelte b/plugins/board-resources/src/components/CreateBoard.svelte index 3c2c0b6e31..ac3b84b7fa 100644 --- a/plugins/board-resources/src/components/CreateBoard.svelte +++ b/plugins/board-resources/src/components/CreateBoard.svelte @@ -16,7 +16,7 @@ 0} + canSave={name.length > 0 && typeId !== undefined} on:close={() => { dispatch('close') }} @@ -65,13 +61,13 @@ { - templateId = evt.detail + typeId = evt.detail }} /> diff --git a/plugins/board-resources/src/components/CreateCard.svelte b/plugins/board-resources/src/components/CreateCard.svelte index 73c555efa2..a1686c8c57 100644 --- a/plugins/board-resources/src/components/CreateCard.svelte +++ b/plugins/board-resources/src/components/CreateCard.svelte @@ -14,10 +14,10 @@ // limitations under the License. --> diff --git a/plugins/board-resources/src/components/EditCard.svelte b/plugins/board-resources/src/components/EditCard.svelte index e61c5ff7c6..ecc36aff31 100644 --- a/plugins/board-resources/src/components/EditCard.svelte +++ b/plugins/board-resources/src/components/EditCard.svelte @@ -63,7 +63,7 @@ let checklists: TodoItem[] = [] const mixins: Mixin[] = [] const allowedCollections = ['labels'] - const ignoreKeys = ['isArchived', 'location', 'title', 'description', 'status', 'number', 'assignee', 'doneState'] + const ignoreKeys = ['isArchived', 'location', 'title', 'description', 'status', 'number', 'assignee'] function change (field: string, value: any) { if (object) { diff --git a/plugins/board-resources/src/components/KanbanView.svelte b/plugins/board-resources/src/components/KanbanView.svelte index 06df18d6e7..696c1fffc1 100644 --- a/plugins/board-resources/src/components/KanbanView.svelte +++ b/plugins/board-resources/src/components/KanbanView.svelte @@ -15,7 +15,7 @@ --> @@ -35,10 +60,10 @@ {#if value}
-
- +
{/if} diff --git a/plugins/board-resources/src/components/popups/CopyCard.svelte b/plugins/board-resources/src/components/popups/CopyCard.svelte index 45cf2ed7c9..f54b95664d 100644 --- a/plugins/board-resources/src/components/popups/CopyCard.svelte +++ b/plugins/board-resources/src/components/popups/CopyCard.svelte @@ -40,7 +40,6 @@ const copy: AttachedData = { status: selected.status, - doneState: null, number: (incResult as any).object.sequence, title, rank: selected.rank, diff --git a/plugins/board-resources/src/components/selectors/StateSelect.svelte b/plugins/board-resources/src/components/selectors/StateSelect.svelte index 8ce23a4795..5176c362cc 100644 --- a/plugins/board-resources/src/components/selectors/StateSelect.svelte +++ b/plugins/board-resources/src/components/selectors/StateSelect.svelte @@ -1,22 +1,37 @@
@@ -75,7 +80,7 @@ width={'fit-content'} value={object.dueDate} shouldRender={object.dueDate !== null && object.dueDate !== undefined} - shouldIgnoreOverdue={object.doneState !== null} + shouldIgnoreOverdue={isDone} onChange={async (e) => { await client.update(object, { dueDate: e }) }} diff --git a/plugins/lead-resources/src/components/Leads.svelte b/plugins/lead-resources/src/components/Leads.svelte index 56ab4c3014..87f93f2bf7 100644 --- a/plugins/lead-resources/src/components/Leads.svelte +++ b/plugins/lead-resources/src/components/Leads.svelte @@ -40,20 +40,10 @@ {#if leads !== undefined && leads > 0} {#if wSection < 640} - +
{:else} -
+
{/if} {:else}
diff --git a/plugins/lead-resources/src/components/LeadsPopup.svelte b/plugins/lead-resources/src/components/LeadsPopup.svelte index 5b47a5e8da..35d3a8e19b 100644 --- a/plugins/lead-resources/src/components/LeadsPopup.svelte +++ b/plugins/lead-resources/src/components/LeadsPopup.svelte @@ -21,4 +21,4 @@ export let value: Customer -
+
diff --git a/plugins/lead/src/index.ts b/plugins/lead/src/index.ts index 089868b1fe..4bd1f3792e 100644 --- a/plugins/lead/src/index.ts +++ b/plugins/lead/src/index.ts @@ -15,16 +15,16 @@ // import type { Contact } from '@hcengineering/contact' -import type { Attribute, Class, Doc, Ref, Timestamp } from '@hcengineering/core' +import type { Attribute, Class, Doc, Ref, Status, Timestamp } from '@hcengineering/core' import { Mixin } from '@hcengineering/core' import type { Asset, IntlString, Plugin } from '@hcengineering/platform' import { plugin } from '@hcengineering/platform' -import type { DoneState, KanbanTemplateSpace, SpaceWithStates, State, Task } from '@hcengineering/task' +import type { Project, ProjectTypeCategory, Task } from '@hcengineering/task' /** * @public */ -export interface Funnel extends SpaceWithStates { +export interface Funnel extends Project { fullDescription?: string attachments?: number } @@ -45,7 +45,7 @@ export interface Customer extends Contact { export interface Lead extends Task { space: Ref attachedTo: Ref - status: Ref + status: Ref startDate: Timestamp | null title: string } @@ -74,8 +74,7 @@ const lead = plugin(leadId, { ConfigLabel: '' as IntlString }, attribute: { - State: '' as Ref>, - DoneState: '' as Ref> + State: '' as Ref> }, icon: { Funnel: '' as Asset, @@ -83,8 +82,8 @@ const lead = plugin(leadId, { LeadApplication: '' as Asset, CreateCustomer: '' as Asset }, - space: { - FunnelTemplates: '' as Ref + category: { + FunnelTypeCategory: '' as Ref } }) diff --git a/plugins/recruit-assets/lang/en.json b/plugins/recruit-assets/lang/en.json index 354dc5c2bc..7f11a92ca7 100644 --- a/plugins/recruit-assets/lang/en.json +++ b/plugins/recruit-assets/lang/en.json @@ -56,7 +56,7 @@ "PersonFirstNamePlaceholder": "First name", "PersonLastNamePlaceholder": "Last name", "PersonLocationPlaceholder": "Location", - "ManageVacancyStatuses": "Manage vacancy templates", + "ManageVacancyStatuses": "Manage vacancy types", "EditVacancy": "Edit", "FullDescription": "Full description", "CreateReview": "Schedule an Review", diff --git a/plugins/recruit-assets/lang/ru.json b/plugins/recruit-assets/lang/ru.json index 95277c5b19..293c87ce51 100644 --- a/plugins/recruit-assets/lang/ru.json +++ b/plugins/recruit-assets/lang/ru.json @@ -56,7 +56,7 @@ "PersonFirstNamePlaceholder": "First name", "PersonLastNamePlaceholder": "Last name", "PersonLocationPlaceholder": "Местоположение", - "ManageVacancyStatuses": "Управление шаблонами вакансии", + "ManageVacancyStatuses": "Управление типами вакансии", "EditVacancy": "Редактировать", "FullDescription": "Детальное описание", diff --git a/plugins/recruit-resources/src/components/ApplicationsPopup.svelte b/plugins/recruit-resources/src/components/ApplicationsPopup.svelte index 65eff90b2a..97ee841f45 100644 --- a/plugins/recruit-resources/src/components/ApplicationsPopup.svelte +++ b/plugins/recruit-resources/src/components/ApplicationsPopup.svelte @@ -33,7 +33,7 @@
diff --git a/plugins/recruit-resources/src/components/CreateApplication.svelte b/plugins/recruit-resources/src/components/CreateApplication.svelte index 30a4d1a66b..20bf08e887 100644 --- a/plugins/recruit-resources/src/components/CreateApplication.svelte +++ b/plugins/recruit-resources/src/components/CreateApplication.svelte @@ -29,7 +29,8 @@ SortingOrder, Space, fillDefaults, - generateId + generateId, + Status as TaskStatus } from '@hcengineering/core' import { OK, Resource, Severity, Status, getResource } from '@hcengineering/platform' import presentation, { @@ -40,7 +41,7 @@ getClient } from '@hcengineering/presentation' import type { Applicant, Candidate, Vacancy } from '@hcengineering/recruit' - import task, { State, calcRank, getStates } from '@hcengineering/task' + import task, { calcRank, getStates } from '@hcengineering/task' import ui, { Button, ColorPopup, @@ -55,12 +56,13 @@ themeStore } from '@hcengineering/ui' import view from '@hcengineering/view' + import { statusStore } from '@hcengineering/view-resources' import { createEventDispatcher } from 'svelte' import recruit from '../plugin' import CandidateCard from './CandidateCard.svelte' import VacancyCard from './VacancyCard.svelte' import VacancyOrgPresenter from './VacancyOrgPresenter.svelte' - import { statusStore } from '@hcengineering/view-resources' + import { typeStore } from '@hcengineering/task-resources' export let space: Ref export let candidate: Ref @@ -79,8 +81,7 @@ $: _candidate = candidate const doc: Applicant = { - status: '' as Ref, - doneState: null, + status: '' as Ref, number: 0, assignee, rank: '', @@ -188,8 +189,8 @@ $: validate(doc, _space, doc._class, _candidate) let states: Array<{ id: number | string; color: number; label: string }> = [] - let selectedState: State | undefined - $: rawStates = getStates(vacancy, $statusStore) + let selectedState: TaskStatus | undefined + $: rawStates = getStates(vacancy, $typeStore, $statusStore.byId) const spaceQuery = createQuery() let vacancy: Vacancy | undefined diff --git a/plugins/recruit-resources/src/components/CreateVacancy.svelte b/plugins/recruit-resources/src/components/CreateVacancy.svelte index e8ef56b15a..56c6dbd1b1 100644 --- a/plugins/recruit-resources/src/components/CreateVacancy.svelte +++ b/plugins/recruit-resources/src/components/CreateVacancy.svelte @@ -28,15 +28,8 @@ import { Card, createQuery, getClient, InlineAttributeBar, MessageBox } from '@hcengineering/presentation' import { Vacancy as VacancyClass } from '@hcengineering/recruit' import tags from '@hcengineering/tags' - import task, { createStates, KanbanTemplate } from '@hcengineering/task' - import tracker, { - calcRank, - Issue, - IssueStatus, - IssueTemplate, - IssueTemplateData, - Project - } from '@hcengineering/tracker' + import task, { calcRank, ProjectType } from '@hcengineering/task' + import tracker, { Issue, IssueStatus, IssueTemplate, IssueTemplateData, Project } from '@hcengineering/tracker' import { Button, Component, @@ -54,9 +47,9 @@ const dispatch = createEventDispatcher() let name: string = '' - let template: KanbanTemplate | undefined - let templateId: Ref | undefined - let appliedTemplateId: Ref | undefined + let template: ProjectType | undefined + let typeId: Ref | undefined + let appliedTemplateId: Ref | undefined let objectId: Ref = generateId() let issueTemplates: FindResult @@ -77,29 +70,29 @@ company: '' as Ref, fullDescription: '', location: '', - states: [] + type: typeId as Ref } export function canClose (): boolean { - return name === '' && templateId !== undefined + return name === '' && typeId !== undefined } const client = getClient() const hierarchy = client.getHierarchy() const templateQ = createQuery() fillDefaults(hierarchy, vacancyData, recruit.class.Vacancy) - $: templateId && - templateQ.query(task.class.KanbanTemplate, { _id: templateId }, (result) => { + $: typeId && + templateQ.query(task.class.ProjectType, { _id: typeId }, (result) => { const { _class, _id, description, ...templateData } = result[0] vacancyData = { ...(templateData as unknown as Data), fullDescription: description } - if (appliedTemplateId !== templateId) { + if (appliedTemplateId !== typeId) { fullDescription = description ?? '' - appliedTemplateId = templateId + appliedTemplateId = typeId } fillDefaults(hierarchy, vacancyData, recruit.class.Vacancy) }) const issueTemplatesQ = createQuery() - $: issueTemplatesQ.query(tracker.class.IssueTemplate, { 'relations._id': templateId }, async (result) => { + $: issueTemplatesQ.query(tracker.class.IssueTemplate, { 'relations._id': typeId }, async (result) => { issueTemplates = result }) @@ -144,8 +137,7 @@ estimation: template.estimation, reports: 0, relations: [{ _id: id, _class: recruit.class.Vacancy }], - childInfo: [], - doneState: null + childInfo: [] }) if ((template.labels?.length ?? 0) > 0) { const tagElements = await client.findAll(tags.class.TagElement, { _id: { $in: template.labels } }) @@ -161,11 +153,8 @@ } async function createVacancy () { - if ( - templateId !== undefined && - (await client.findOne(task.class.KanbanTemplate, { _id: templateId })) === undefined - ) { - throw Error(`Failed to find target kanban template: ${templateId}`) + if (typeId === undefined) { + throw Error(`Failed to find target project type: ${typeId}`) } const sequence = await client.findOne(task.class.Sequence, { attachedTo: recruit.class.Vacancy }) @@ -175,13 +164,6 @@ const incResult = await client.update(sequence, { $inc: { sequence: 1 } }, true) - const [states, doneStates] = await createStates( - client, - recruit.attribute.State, - recruit.attribute.DoneState, - templateId - ) - const id = await client.createDoc( recruit.class.Vacancy, core.space.Space, @@ -195,9 +177,7 @@ number: (incResult as any).object.sequence, company, members: [getCurrentAccount()._id], - templateId, - states, - doneStates + type: typeId }, objectId ) @@ -220,9 +200,9 @@ let descriptionBox: AttachmentStyledBox - function handleTemplateChange (evt: CustomEvent>): void { - if (templateId == null) { - templateId = evt.detail + function handleTypeChange (evt: CustomEvent>): void { + if (typeId == null) { + typeId = evt.detail return } // Template is already specified, ask to replace. @@ -236,7 +216,7 @@ 'top', (result?: boolean) => { if (result === true) { - templateId = evt.detail ?? undefined + typeId = evt.detail ?? undefined } } ) @@ -301,15 +281,15 @@ create={{ component: contact.component.CreateOrganization, label: contact.string.CreateOrganization }} /> diff --git a/plugins/recruit-resources/src/components/KanbanCard.svelte b/plugins/recruit-resources/src/components/KanbanCard.svelte index 55ee530c21..c915cf1913 100644 --- a/plugins/recruit-resources/src/components/KanbanCard.svelte +++ b/plugins/recruit-resources/src/components/KanbanCard.svelte @@ -21,11 +21,12 @@ import notification from '@hcengineering/notification' import { getClient } from '@hcengineering/presentation' import recruit, { Applicant, Candidate } from '@hcengineering/recruit' + import task from '@hcengineering/task' import { AssigneePresenter, StateRefPresenter } from '@hcengineering/task-resources' import tracker from '@hcengineering/tracker' import { Component, DueDatePresenter } from '@hcengineering/ui' import { BuildModelKey } from '@hcengineering/view' - import { DocNavLink, ObjectPresenter, enabledConfig } from '@hcengineering/view-resources' + import { DocNavLink, ObjectPresenter, enabledConfig, statusStore } from '@hcengineering/view-resources' import ApplicationPresenter from './ApplicationPresenter.svelte' export let object: WithLookup @@ -41,6 +42,10 @@ $: channels = (object.$lookup?.attachedTo as WithLookup)?.$lookup?.channels $: company = object?.$lookup?.space?.company + + $: status = $statusStore.byId.get(object.status) + + $: isDone = status?.category === task.statusCategory.Lost || status?.category === task.statusCategory.Won @@ -112,7 +117,7 @@ kind={'link-bordered'} value={object.dueDate} shouldRender={object.dueDate !== null && object.dueDate !== undefined} - shouldIgnoreOverdue={object.doneState !== null} + shouldIgnoreOverdue={isDone} onChange={async (e) => { await client.update(object, { dueDate: e }) }} diff --git a/plugins/recruit-resources/src/components/MoveApplication.svelte b/plugins/recruit-resources/src/components/MoveApplication.svelte index 59d9b45f4c..67571a10f7 100644 --- a/plugins/recruit-resources/src/components/MoveApplication.svelte +++ b/plugins/recruit-resources/src/components/MoveApplication.svelte @@ -15,11 +15,10 @@ @@ -43,8 +43,8 @@
onShortDescriptionChange(template.shortDescription ?? '')} + bind:value={type.shortDescription} + on:change={() => onShortDescriptionChange(type.shortDescription ?? '')} />
@@ -52,12 +52,12 @@
- {#key template._id} + {#key type._id} onDescriptionChange(evt.detail)} /> {/key} @@ -80,16 +80,16 @@ labelParams={{ subIssues: 0 }} kind={'ghost'} size={'small'} - on:click={() => showPopup(tracker.component.CreateIssueTemplate, { relatedTo: template })} + on:click={() => showPopup(tracker.component.CreateIssueTemplate, { relatedTo: type })} />
- +
{#if customKeys && customKeys.length > 0}
- +
{/if} diff --git a/plugins/recruit-resources/src/components/organizations/VacancyListApplicationsPopup.svelte b/plugins/recruit-resources/src/components/organizations/VacancyListApplicationsPopup.svelte index ea1bdad524..7c45707bc0 100644 --- a/plugins/recruit-resources/src/components/organizations/VacancyListApplicationsPopup.svelte +++ b/plugins/recruit-resources/src/components/organizations/VacancyListApplicationsPopup.svelte @@ -15,7 +15,6 @@
-
- {#each folders as f (f._id)} + {#each categories as f (f._id)} {#if hasResource(f.icon)} -
select(f)}> +
select(f)}>
@@ -55,7 +60,7 @@
- {#if f._id === folder?._id} + {#if f._id === category?._id}
{/if}
diff --git a/plugins/setting-resources/src/components/statuses/ManageProjects.svelte b/plugins/setting-resources/src/components/statuses/ManageProjects.svelte new file mode 100644 index 0000000000..29e5f63e1c --- /dev/null +++ b/plugins/setting-resources/src/components/statuses/ManageProjects.svelte @@ -0,0 +1,63 @@ + + + +
+
+
+
+
+
+
+ +
+
+ {#if category !== undefined} + + {/if} +
+
+ {#if type !== undefined} + + {/if} +
+
+
diff --git a/plugins/setting-resources/src/components/statuses/ManageTemplates.svelte b/plugins/setting-resources/src/components/statuses/ManageTemplates.svelte deleted file mode 100644 index 00bc83b797..0000000000 --- a/plugins/setting-resources/src/components/statuses/ManageTemplates.svelte +++ /dev/null @@ -1,77 +0,0 @@ - - - -
-
-
-
-
-
-
- -
-
- {#if folder !== undefined} - - {/if} -
-
- {#if template !== undefined} - - {/if} -
-
-
diff --git a/plugins/setting-resources/src/components/statuses/Templates.svelte b/plugins/setting-resources/src/components/statuses/Templates.svelte deleted file mode 100644 index e8f979bf87..0000000000 --- a/plugins/setting-resources/src/components/statuses/Templates.svelte +++ /dev/null @@ -1,120 +0,0 @@ - - - -
-
-
- {#each templates as t (t._id)} - -
select(t)}> - - {#if templates.length > 1} -
{ - showPopup(ContextMenu, { object: t }, eventToHTMLElement(ev), () => {}) - }} - > - -
- {/if} -
- {/each} -
diff --git a/plugins/setting-resources/src/components/statuses/Types.svelte b/plugins/setting-resources/src/components/statuses/Types.svelte new file mode 100644 index 0000000000..4b3b94062d --- /dev/null +++ b/plugins/setting-resources/src/components/statuses/Types.svelte @@ -0,0 +1,103 @@ + + + +
+
+
+ {#each types as t (t._id)} + +
select(t)}> + + {#if types.length > 1} +
{ + showPopup(ContextMenu, { object: t }, eventToHTMLElement(ev), () => {}) + }} + > + +
+ {/if} +
+ {/each} +
diff --git a/plugins/setting-resources/src/index.ts b/plugins/setting-resources/src/index.ts index 56da94ca69..a1065007c0 100644 --- a/plugins/setting-resources/src/index.ts +++ b/plugins/setting-resources/src/index.ts @@ -29,7 +29,7 @@ import Password from './components/Password.svelte' import Privacy from './components/Privacy.svelte' import Profile from './components/Profile.svelte' import Settings from './components/Settings.svelte' -import ManageTemplates from './components/statuses/ManageTemplates.svelte' +import ManageProjects from './components/statuses/ManageProjects.svelte' import Support from './components/Support.svelte' import Terms from './components/Terms.svelte' import BooleanTypeEditor from './components/typeEditors/BooleanTypeEditor.svelte' @@ -84,7 +84,7 @@ export default async (): Promise => ({ Support, Privacy, Terms, - ManageTemplates, + ManageProjects, ClassSetting, StringTypeEditor, HyperlinkTypeEditor, diff --git a/plugins/setting/src/index.ts b/plugins/setting/src/index.ts index 529c33e758..f5b8494629 100644 --- a/plugins/setting/src/index.ts +++ b/plugins/setting/src/index.ts @@ -101,7 +101,7 @@ export default plugin(settingId, { Password: '' as Ref, Setting: '' as Ref, Integrations: '' as Ref, - ManageTemplates: '' as Ref, + ManageProjects: '' as Ref, Support: '' as Ref, Privacy: '' as Ref, Terms: '' as Ref, @@ -129,7 +129,7 @@ export default plugin(settingId, { Password: '' as AnyComponent, WorkspaceSettings: '' as AnyComponent, Integrations: '' as AnyComponent, - ManageTemplates: '' as AnyComponent, + ManageProjects: '' as AnyComponent, Support: '' as AnyComponent, Privacy: '' as AnyComponent, Terms: '' as AnyComponent, @@ -140,12 +140,11 @@ export default plugin(settingId, { Setting: '' as IntlString, WorkspaceSetting: '' as IntlString, Integrations: '' as IntlString, - ManageTemplates: '' as IntlString, + ManageProjects: '' as IntlString, Support: '' as IntlString, Privacy: '' as IntlString, Terms: '' as IntlString, - Folders: '' as IntlString, - Templates: '' as IntlString, + Categories: '' as IntlString, Delete: '' as IntlString, Disconnect: '' as IntlString, Add: '' as IntlString, diff --git a/plugins/task-assets/lang/en.json b/plugins/task-assets/lang/en.json index db29a9721a..8bff5fef02 100644 --- a/plugins/task-assets/lang/en.json +++ b/plugins/task-assets/lang/en.json @@ -20,9 +20,6 @@ "TaskComments": "Comments", "TaskLabels": "Labels", "TaskAssignee": "Assignee", - "StateTemplateTitle": "Title", - "StateTemplateColor": "Color", - "KanbanTemplateTitle": "Title", "Rank": "Rank", "TaskCreateLabel": "Task", "EditStates": "Edit states", @@ -76,6 +73,8 @@ "RelatedIssues": "Related processes", "StatusName": "Status name", "StatusPopupTitle": "Create new status or edit name for existing", - "NameAlreadyExists": "This name already exists for this status type" + "NameAlreadyExists": "This name already exists for this status type", + "ProjectType": "Project type", + "ProjectTypes": "Project types" } } \ No newline at end of file diff --git a/plugins/task-assets/lang/ru.json b/plugins/task-assets/lang/ru.json index 1348ed4daa..3bb46bf489 100644 --- a/plugins/task-assets/lang/ru.json +++ b/plugins/task-assets/lang/ru.json @@ -20,9 +20,6 @@ "TaskComments": "Комментарии", "TaskLabels": "Ярлыки", "TaskAssignee": "Назначен", - "StateTemplateTitle": "Заголовок", - "StateTemplateColor": "Цвет", - "KanbanTemplateTitle": "Заголовок", "Rank": "Ранк", "TaskCreateLabel": "Задача", "EditStates": "Редактировать статусы", @@ -76,6 +73,8 @@ "RelatedIssues": "Связанные процессы", "StatusName": "Имя статуса", "StatusPopupTitle": "Создание статуса и изменение имени существующего", - "NameAlreadyExists": "Данное имя уже используется другим статусом этого типа" + "NameAlreadyExists": "Данное имя уже используется другим статусом этого типа", + "ProjectType": "Тип проекта", + "ProjectTypes": "Типы проектов" } } \ No newline at end of file diff --git a/plugins/task-resources/src/components/AssignedTasks.svelte b/plugins/task-resources/src/components/AssignedTasks.svelte index 39b47fc75c..114d8c7f83 100644 --- a/plugins/task-resources/src/components/AssignedTasks.svelte +++ b/plugins/task-resources/src/components/AssignedTasks.svelte @@ -14,11 +14,11 @@ --> - - dispatch('close')} -> - - - {#if !canSave} - - diff --git a/plugins/task-resources/src/components/Dashboard.svelte b/plugins/task-resources/src/components/Dashboard.svelte index a2c65f461c..cf3f0c25d1 100644 --- a/plugins/task-resources/src/components/Dashboard.svelte +++ b/plugins/task-resources/src/components/Dashboard.svelte @@ -13,46 +13,29 @@ // limitations under the License. --> -{#if docMatch} - { - changeStatus(evt.detail === null ? null : evt.detail?._id) - }} - {placeholder} - {width} - {embedded} - on:changeContent - > - -
- -
-
-
-{:else} -
dispatch('changeContent')}> -
-
-{/if} + + diff --git a/plugins/task-resources/src/components/StatusTableView.svelte b/plugins/task-resources/src/components/StatusTableView.svelte index a9c55ea1da..9c92ade666 100644 --- a/plugins/task-resources/src/components/StatusTableView.svelte +++ b/plugins/task-resources/src/components/StatusTableView.svelte @@ -14,42 +14,45 @@ // limitations under the License. --> @@ -76,7 +76,7 @@
- + {:else} diff --git a/plugins/task-resources/src/components/TypesView.svelte b/plugins/task-resources/src/components/TypesView.svelte new file mode 100644 index 0000000000..f4e409d313 --- /dev/null +++ b/plugins/task-resources/src/components/TypesView.svelte @@ -0,0 +1,168 @@ + + + +
+
+ + {#if modeSelectorProps !== undefined && modeSelectorProps.config.length > 1} + + {/if} +
+ +
+ + {#if createLabel && createComponent} +
+
+
+
+ + +
+ +
+
+ + +
+
+ +{#if !viewlet?.$lookup?.descriptor?.component || viewlet?.attachTo !== _class || (preference !== undefined && viewlet?._id !== preference.attachedTo)} + +{:else if viewOptions && viewlet} + { + resultQuery = { ...query, ...e.detail } + }} + /> + +{/if} diff --git a/plugins/task-resources/src/components/kanban/KanbanDragDone.svelte b/plugins/task-resources/src/components/kanban/KanbanDragDone.svelte index bb2238d691..d61f59d2cd 100644 --- a/plugins/task-resources/src/components/kanban/KanbanDragDone.svelte +++ b/plugins/task-resources/src/components/kanban/KanbanDragDone.svelte @@ -13,39 +13,34 @@ // limitations under the License. --> - - diff --git a/plugins/task-resources/src/components/kanban/KanbanView.svelte b/plugins/task-resources/src/components/kanban/KanbanView.svelte index 37d90bd1f0..33bcc2ab6f 100644 --- a/plugins/task-resources/src/components/kanban/KanbanView.svelte +++ b/plugins/task-resources/src/components/kanban/KanbanView.svelte @@ -14,7 +14,7 @@ // limitations under the License. --> {#await cardPresenter then presenter} diff --git a/plugins/task-resources/src/components/kanban/ProjectEditor.svelte b/plugins/task-resources/src/components/kanban/ProjectEditor.svelte new file mode 100644 index 0000000000..9c02a3dba6 --- /dev/null +++ b/plugins/task-resources/src/components/kanban/ProjectEditor.svelte @@ -0,0 +1,51 @@ + + + + diff --git a/plugins/task-resources/src/components/kanban/KanbanTemplateSelector.svelte b/plugins/task-resources/src/components/kanban/ProjectTypeSelector.svelte similarity index 60% rename from plugins/task-resources/src/components/kanban/KanbanTemplateSelector.svelte rename to plugins/task-resources/src/components/kanban/ProjectTypeSelector.svelte index cc43d4db62..fd6931ecd0 100644 --- a/plugins/task-resources/src/components/kanban/KanbanTemplateSelector.svelte +++ b/plugins/task-resources/src/components/kanban/ProjectTypeSelector.svelte @@ -13,40 +13,38 @@ // limitations under the License. --> @@ -55,6 +53,7 @@ {items} {kind} {size} + {disabled} icon={task.icon.ManageTemplates} bind:selected={selectedItem} label={plugin.string.States} diff --git a/plugins/task-resources/src/components/state/DoneStateEditor.svelte b/plugins/task-resources/src/components/state/DoneStateEditor.svelte deleted file mode 100644 index bbf37c0593..0000000000 --- a/plugins/task-resources/src/components/state/DoneStateEditor.svelte +++ /dev/null @@ -1,88 +0,0 @@ - - - - diff --git a/plugins/task-resources/src/components/state/DoneStatePresenter.svelte b/plugins/task-resources/src/components/state/DoneStatePresenter.svelte deleted file mode 100644 index b8ba866d32..0000000000 --- a/plugins/task-resources/src/components/state/DoneStatePresenter.svelte +++ /dev/null @@ -1,41 +0,0 @@ - - - -{#if value} -
-
- -
- {#if showTitle} - {value.name} - {/if} -
-{/if} diff --git a/plugins/task-resources/src/components/state/DoneStateRefPresenter.svelte b/plugins/task-resources/src/components/state/DoneStateRefPresenter.svelte deleted file mode 100644 index 9b62e58911..0000000000 --- a/plugins/task-resources/src/components/state/DoneStateRefPresenter.svelte +++ /dev/null @@ -1,45 +0,0 @@ - - - -{#if value} - {#if onChange !== undefined && state !== undefined} - - {:else} - - {/if} -{:else} -
-
- -
-{/if} diff --git a/plugins/task-resources/src/components/state/DoneStatesPopup.svelte b/plugins/task-resources/src/components/state/DoneStatesPopup.svelte deleted file mode 100644 index 63b00684c2..0000000000 --- a/plugins/task-resources/src/components/state/DoneStatesPopup.svelte +++ /dev/null @@ -1,85 +0,0 @@ - - - -
dispatch('changeContent')}> -