From 2a6f5f73900d92c64ff2b3c2bda47565e229b557 Mon Sep 17 00:00:00 2001
From: Andrey Sobolev <haiodo@users.noreply.github.com>
Date: Thu, 16 Dec 2021 16:06:56 +0700
Subject: [PATCH] Fix migration (#650)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
---
 models/lead/src/migration.ts    |  34 +++-----
 models/recruit/src/migration.ts |  52 ++++++------
 models/task/src/migration.ts    | 137 ++++++++++++++++++++------------
 3 files changed, 120 insertions(+), 103 deletions(-)

diff --git a/models/lead/src/migration.ts b/models/lead/src/migration.ts
index de27373f34..90e88e9235 100644
--- a/models/lead/src/migration.ts
+++ b/models/lead/src/migration.ts
@@ -15,22 +15,29 @@
 //
 
 import { Doc, TxOperations } from '@anticrm/core'
-import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model'
+import { MigrateOperation, MigrationClient, MigrationResult, MigrationUpgradeClient } from '@anticrm/model'
 import core from '@anticrm/model-core'
-import task from '@anticrm/model-task'
+import task, { DOMAIN_TASK } from '@anticrm/model-task'
 import { createKanban } from '@anticrm/lead'
 import lead from './plugin'
 
+function logInfo (msg: string, result: MigrationResult): void {
+  if (result.updated > 0) {
+    console.log(`Lead: Migrate ${msg} ${result.updated}`)
+  }
+}
+
 export const leadOperation: MigrateOperation = {
   async migrate (client: MigrationClient): Promise<void> {
-
+    // Update done states for tasks
+    logInfo('lead done states', await client.update(DOMAIN_TASK, { _class: lead.class.Lead, doneState: { $exists: false } }, { doneState: null }))
   },
   async upgrade (client: MigrationUpgradeClient): Promise<void> {
     console.log('Lead: Performing model upgrades')
 
     const ops = new TxOperations(client, core.account.System)
     if (await client.findOne(task.class.Kanban, { attachedTo: lead.space.DefaultFunnel }) === undefined) {
-      console.info('Create kanban for default funnel.')
+      console.info('Lead: Create kanban for default funnel.')
       await createKanban(lead.space.DefaultFunnel, async (_class, space, data, id) => {
         const doc = await ops.findOne<Doc>(_class, { _id: id })
         if (doc === undefined) {
@@ -44,29 +51,14 @@ export const leadOperation: MigrateOperation = {
     }
 
     if (await client.findOne(task.class.Sequence, { attachedTo: lead.class.Lead }) === undefined) {
-      console.info('Create sequence for default task project.')
+      console.info('Lead: Create sequence for default task project.')
       // We need to create sequence
       await ops.createDoc(task.class.Sequence, task.space.Sequence, {
         attachedTo: lead.class.Lead,
         sequence: 0
       })
     } else {
-      console.log('Task: => sequence is ok')
+      console.log('Lead: => sequence is ok')
     }
-
-    const outdatedLeads = (await client.findAll(lead.class.Lead, {}))
-      .filter((x) => x.doneState === undefined)
-
-    await Promise.all(
-      outdatedLeads.map(async (lead) => {
-        console.info('Upgrade lead:', lead._id)
-
-        try {
-          await ops.updateDoc(lead._class, lead.space, lead._id, { doneState: null })
-        } catch (err: unknown) {
-          console.error(err)
-        }
-      })
-    )
   }
 }
diff --git a/models/recruit/src/migration.ts b/models/recruit/src/migration.ts
index 20d2551102..575bad5f0b 100644
--- a/models/recruit/src/migration.ts
+++ b/models/recruit/src/migration.ts
@@ -13,42 +13,36 @@
 // limitations under the License.
 //
 
-import { TxOperations } from '@anticrm/core'
-import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model'
-import core from '@anticrm/model-core'
-
+import { MigrateOperation, MigrationClient, MigrationResult, MigrationUpgradeClient } from '@anticrm/model'
+import contact, { DOMAIN_CONTACT } from '@anticrm/model-contact'
+import { DOMAIN_TASK } from '@anticrm/model-task'
 import recruit from './plugin'
 
+function logInfo (msg: string, result: MigrationResult): void {
+  if (result.updated > 0) {
+    console.log(`Recruit: Migrate ${msg} ${result.updated}`)
+  }
+}
 export const recruitOperation: MigrateOperation = {
   async migrate (client: MigrationClient): Promise<void> {
+    logInfo('done for Applicants', await client.update(DOMAIN_TASK, { _class: recruit.class.Applicant, doneState: { $exists: false } }, { doneState: null }))
 
+    logInfo('$move employee => assignee', await client.update(
+      DOMAIN_TASK,
+      { _class: recruit.class.Applicant, employee: { $exists: true } },
+      { $rename: { employee: 'assignee' } }
+    ))
+
+    const employees = (await client.find(DOMAIN_CONTACT, { _class: contact.class.Employee })).map(emp => emp._id)
+
+    // update assignee to unassigned if there is no employee exists.
+    logInfo('applicants wrong assignee', await client.update(
+      DOMAIN_TASK,
+      { _class: recruit.class.Applicant, assignee: { $not: { $in: employees } } },
+      { assignee: null }
+    ))
   },
   async upgrade (client: MigrationUpgradeClient): Promise<void> {
     console.log('Recruit: Performing model upgrades')
-
-    const ops = new TxOperations(client, core.account.System)
-
-    const outdatedApplications = (await client.findAll(recruit.class.Applicant, {}))
-      .filter((x) => x.doneState === undefined)
-
-    await Promise.all(
-      outdatedApplications.map(async (application) => {
-        console.info('Upgrade application:', application._id)
-
-        try {
-          await ops.updateCollection(
-            application._class,
-            application.space,
-            application._id,
-            application.attachedTo,
-            application.attachedToClass,
-            application.collection,
-            { doneState: null }
-          )
-        } catch (err: unknown) {
-          console.error(err)
-        }
-      })
-    )
   }
 }
diff --git a/models/task/src/migration.ts b/models/task/src/migration.ts
index 544b9aaa14..28a8551860 100644
--- a/models/task/src/migration.ts
+++ b/models/task/src/migration.ts
@@ -14,7 +14,13 @@
 //
 
 import { Class, Doc, Domain, DOMAIN_TX, Ref, TxCUD, TxOperations } from '@anticrm/core'
-import { MigrateOperation, MigrateUpdate, MigrationClient, MigrationResult, MigrationUpgradeClient } from '@anticrm/model'
+import {
+  MigrateOperation,
+  MigrateUpdate,
+  MigrationClient,
+  MigrationResult,
+  MigrationUpgradeClient
+} from '@anticrm/model'
 import core from '@anticrm/model-core'
 import { createProjectKanban } from '@anticrm/task'
 import { DOMAIN_TASK, DOMAIN_STATE, DOMAIN_KANBAN } from '.'
@@ -22,14 +28,22 @@ import task from './plugin'
 
 function logInfo (msg: string, result: MigrationResult): void {
   if (result.updated > 0) {
-    console.log(`Tasks: Migrate ${msg} ${result.updated}`)
+    console.log(`Task: Migrate ${msg} ${result.updated}`)
   }
 }
-async function migrateClass<T extends Doc> (client: MigrationClient, domain: Domain, from: Ref<Class<Doc>>, to: Ref<Class<T>>, extraOps: MigrateUpdate<T> = {}, txExtraOps: MigrateUpdate<TxCUD<Doc>> = {}): Promise<void> {
-  logInfo(`Migrate ${from} => ${to}: `,
-    await client.update<Doc>(domain, { _class: from }, { ...extraOps, _class: to }))
-  logInfo(`Migrate ${from} => ${to} Transactions`,
-    await client.update<TxCUD<Doc>>(DOMAIN_TX, { objectClass: from }, { ...txExtraOps, objectClass: to }))
+async function migrateClass<T extends Doc> (
+  client: MigrationClient,
+  domain: Domain,
+  from: Ref<Class<Doc>>,
+  to: Ref<Class<T>>,
+  extraOps: MigrateUpdate<T> = {},
+  txExtraOps: MigrateUpdate<TxCUD<Doc>> = {}
+): Promise<void> {
+  logInfo(`${from} => ${to}: `, await client.update<Doc>(domain, { _class: from }, { ...extraOps, _class: to }))
+  logInfo(
+    `${from} => ${to} Transactions`,
+    await client.update<TxCUD<Doc>>(DOMAIN_TX, { objectClass: from }, { ...txExtraOps, objectClass: to })
+  )
 }
 
 export const taskOperation: MigrateOperation = {
@@ -40,30 +54,62 @@ export const taskOperation: MigrateOperation = {
     await migrateClass(client, DOMAIN_STATE, 'core:class:WonState' as Ref<Class<Doc>>, task.class.WonState)
     await migrateClass(client, DOMAIN_STATE, 'core:class:LostState' as Ref<Class<Doc>>, task.class.LostState)
     await migrateClass(client, DOMAIN_KANBAN, 'view:class:Kanban' as Ref<Class<Doc>>, task.class.Kanban)
-    await migrateClass(client, DOMAIN_KANBAN, 'view:class:Sequence' as Ref<Class<Doc>>, task.class.Sequence, { space: task.space.Sequence }, { objectSpace: task.space.Sequence })
+    await migrateClass(
+      client,
+      DOMAIN_KANBAN,
+      'view:class:Sequence' as Ref<Class<Doc>>,
+      task.class.Sequence,
+      { space: task.space.Sequence },
+      { objectSpace: task.space.Sequence }
+    )
 
     // Update attached to for task
-    await client.update(DOMAIN_KANBAN, { _class: task.class.Sequence, attachedTo: task.class.Task }, { attachedTo: task.class.Issue })
+    await client.update(
+      DOMAIN_KANBAN,
+      { _class: task.class.Sequence, attachedTo: task.class.Task },
+      { attachedTo: task.class.Issue }
+    )
 
     await migrateClass(client, DOMAIN_KANBAN, 'view:class:KanbanTemplate' as Ref<Class<Doc>>, task.class.KanbanTemplate)
     await migrateClass(client, DOMAIN_KANBAN, 'view:class:StateTemplate' as Ref<Class<Doc>>, task.class.StateTemplate)
-    await migrateClass(client, DOMAIN_KANBAN, 'view:class:DoneStateTemplate' as Ref<Class<Doc>>, task.class.DoneStateTemplate)
-    await migrateClass(client, DOMAIN_KANBAN, 'view:class:LostStateTemplate' as Ref<Class<Doc>>, task.class.LostStateTemplate)
+    await migrateClass(
+      client,
+      DOMAIN_KANBAN,
+      'view:class:DoneStateTemplate' as Ref<Class<Doc>>,
+      task.class.DoneStateTemplate
+    )
+    await migrateClass(
+      client,
+      DOMAIN_KANBAN,
+      'view:class:LostStateTemplate' as Ref<Class<Doc>>,
+      task.class.LostStateTemplate
+    )
 
-    await client.move('recruit' as Domain, {
-      _class: 'recruit:class:Applicant' as Ref<Class<Doc>>
-    }, DOMAIN_TASK)
+    await client.move(
+      'recruit' as Domain,
+      {
+        _class: 'recruit:class:Applicant' as Ref<Class<Doc>>
+      },
+      DOMAIN_TASK
+    )
 
-    await client.move('lead' as Domain, {
-      _class: 'lead:class:Lead' as Ref<Class<Doc>>
-    }, DOMAIN_TASK)
+    await client.move(
+      'lead' as Domain,
+      {
+        _class: 'lead:class:Lead' as Ref<Class<Doc>>
+      },
+      DOMAIN_TASK
+    )
+
+    // Update done states for tasks
+    await client.update(DOMAIN_TASK, { _class: task.class.Issue, doneState: { $exists: false } }, { doneState: null })
   },
   async upgrade (client: MigrationUpgradeClient): Promise<void> {
     console.log('Task: Performing model upgrades')
 
     const ops = new TxOperations(client, core.account.System)
-    if (await client.findOne(task.class.Sequence, { attachedTo: task.class.Issue }) === undefined) {
-      console.info('Create sequence for default task project.')
+    if ((await client.findOne(task.class.Sequence, { attachedTo: task.class.Issue })) === undefined) {
+      console.info('Task: Create sequence for default task project.')
       // We need to create sequence
       await ops.createDoc(task.class.Sequence, task.space.Sequence, {
         attachedTo: task.class.Issue,
@@ -72,8 +118,8 @@ export const taskOperation: MigrateOperation = {
     } else {
       console.log('Task: => sequence is ok')
     }
-    if (await client.findOne(task.class.Kanban, { attachedTo: task.space.TasksPublic }) === undefined) {
-      console.info('Create kanban for default task project.')
+    if ((await client.findOne(task.class.Kanban, { attachedTo: task.space.TasksPublic })) === undefined) {
+      console.info('Task: Create kanban for default task project.')
       await createProjectKanban(task.space.TasksPublic, async (_class, space, data, id) => {
         const doc = await ops.findOne<Doc>(_class, { _id: id })
         if (doc === undefined) {
@@ -88,41 +134,26 @@ export const taskOperation: MigrateOperation = {
 
     console.log('View: Performing model upgrades')
 
-    const kanbans = (await client.findAll(task.class.Kanban, {}))
-      .filter((kanban) => kanban.doneStates == null)
+    const kanbans = (await client.findAll(task.class.Kanban, {})).filter((kanban) => kanban.doneStates == null)
 
     await Promise.all(
-      kanbans
-        .map(async (kanban) => {
-          console.log(`Updating kanban: ${kanban._id}`)
-          try {
-            const doneStates = await Promise.all([
-              ops.createDoc(task.class.WonState, kanban.space, {
-                title: 'Won'
-              }),
-              ops.createDoc(task.class.LostState, kanban.space, {
-                title: 'Lost'
-              })
-            ])
-
-            await ops.updateDoc(kanban._class, kanban.space, kanban._id, {
-              doneStates
-            })
-          } catch (e) {
-            console.error(e)
-          }
-        }))
-
-    const outdatedTasks = (await client.findAll(task.class.Task, {}))
-      .filter((x) => x.doneState === undefined)
-
-    await Promise.all(
-      outdatedTasks.map(async (task) => {
-        console.info('Upgrade task:', task._id)
+      kanbans.map(async (kanban) => {
+        console.log(`Updating kanban: ${kanban._id}`)
         try {
-          await ops.updateDoc(task._class, task.space, task._id, { doneState: null })
-        } catch (err: unknown) {
-          console.error(err)
+          const doneStates = await Promise.all([
+            ops.createDoc(task.class.WonState, kanban.space, {
+              title: 'Won'
+            }),
+            ops.createDoc(task.class.LostState, kanban.space, {
+              title: 'Lost'
+            })
+          ])
+
+          await ops.updateDoc(kanban._class, kanban.space, kanban._id, {
+            doneStates
+          })
+        } catch (e) {
+          console.error(e)
         }
       })
     )