Merged PR 547: 音声ファイルアップロード完了API修正(repository実装含む)
## 概要 [Task2971: 音声ファイルアップロード完了API修正(repository実装含む)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2971) - 音声ファイルアップロード完了API修正 - 自動ルーティング処理を追加 - authorIDとworktypeの組み合わせでワークフロー(ルーティングルール)を取得し、そのワークフローに従って、タスクのチェックアウト候補を設定する。 - チェックアウト候補に設定したユーザーに対して通知を行う処理を追加 ## レビューポイント - 自動ルーティング処理を実装しているメソッドのメソッド名はこれでよいか - ほかに思いつかなかったので - AudioOptionItemのentityの定義はあっている? - がタスクにあるaudio_file_idに紐づいている感じになっている - 自動ルーティング処理で失敗したときの挙動は認識あっているか - エラーログだけ出してAPIとしては成功とする - テストケースは足りているか - 古い形式で記述されていたタスク作成のテストを新しい形で作り替えたが、反映漏れている部分はあるか ## UIの変更 - Before/Afterのスクショなど - スクショ置き場 ## 動作確認状況 - ローカルで確認 ## 補足 - 相談、参考資料などがあれば
This commit is contained in:
parent
8a2ca2b786
commit
36716dc408
@ -1 +1 @@
|
|||||||
export const ADB2C_PREFIX = "adb2c-external-id:"
|
export const ADB2C_PREFIX = 'adb2c-external-id:';
|
||||||
|
|||||||
8
dictation_server/src/common/cache/index.ts
vendored
8
dictation_server/src/common/cache/index.ts
vendored
@ -6,8 +6,8 @@ import { ADB2C_PREFIX } from './constants';
|
|||||||
* @returns キャッシュのキー
|
* @returns キャッシュのキー
|
||||||
*/
|
*/
|
||||||
export const makeADB2CKey = (externalId: string): string => {
|
export const makeADB2CKey = (externalId: string): string => {
|
||||||
return `${ADB2C_PREFIX}${externalId}`;
|
return `${ADB2C_PREFIX}${externalId}`;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ADB2Cのユーザー格納用のキーから外部ユーザーIDを取得する
|
* ADB2Cのユーザー格納用のキーから外部ユーザーIDを取得する
|
||||||
@ -15,5 +15,5 @@ export const makeADB2CKey = (externalId: string): string => {
|
|||||||
* @returns 外部ユーザーID
|
* @returns 外部ユーザーID
|
||||||
*/
|
*/
|
||||||
export const restoreAdB2cID = (key: string): string => {
|
export const restoreAdB2cID = (key: string): string => {
|
||||||
return key.replace(ADB2C_PREFIX, '');
|
return key.replace(ADB2C_PREFIX, '');
|
||||||
}
|
};
|
||||||
|
|||||||
@ -7,11 +7,5 @@ import type {
|
|||||||
} from './types';
|
} from './types';
|
||||||
import { isIDToken } from './typeguard';
|
import { isIDToken } from './typeguard';
|
||||||
|
|
||||||
export type {
|
export type { AccessToken, B2cMetadata, IDToken, JwkSignKey, RefreshToken };
|
||||||
AccessToken,
|
|
||||||
B2cMetadata,
|
|
||||||
IDToken,
|
|
||||||
JwkSignKey,
|
|
||||||
RefreshToken,
|
|
||||||
};
|
|
||||||
export { isIDToken };
|
export { isIDToken };
|
||||||
|
|||||||
@ -150,8 +150,9 @@ export const createWorktype = async (
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return (await datasource
|
||||||
return worktype;
|
.getRepository(Worktype)
|
||||||
|
.findOne({ where: { id: worktype.id } })) as Worktype;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Worktypeを取得する
|
// Worktypeを取得する
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import { AudioOptionItemsRepositoryModule } from '../../repositories/audio_optio
|
|||||||
import { TasksRepositoryModule } from '../../repositories/tasks/tasks.repository.module';
|
import { TasksRepositoryModule } from '../../repositories/tasks/tasks.repository.module';
|
||||||
import { BlobstorageModule } from '../../gateways/blobstorage/blobstorage.module';
|
import { BlobstorageModule } from '../../gateways/blobstorage/blobstorage.module';
|
||||||
import { TemplateFilesRepositoryModule } from '../../repositories/template_files/template_files.repository.module';
|
import { TemplateFilesRepositoryModule } from '../../repositories/template_files/template_files.repository.module';
|
||||||
|
import { UserGroupsRepositoryModule } from '../../repositories/user_groups/user_groups.repository.module';
|
||||||
|
import { NotificationhubModule } from '../../gateways/notificationhub/notificationhub.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -16,6 +18,8 @@ import { TemplateFilesRepositoryModule } from '../../repositories/template_files
|
|||||||
TasksRepositoryModule,
|
TasksRepositoryModule,
|
||||||
BlobstorageModule,
|
BlobstorageModule,
|
||||||
TemplateFilesRepositoryModule,
|
TemplateFilesRepositoryModule,
|
||||||
|
UserGroupsRepositoryModule,
|
||||||
|
NotificationhubModule,
|
||||||
],
|
],
|
||||||
providers: [FilesService],
|
providers: [FilesService],
|
||||||
controllers: [FilesController],
|
controllers: [FilesController],
|
||||||
|
|||||||
@ -7,7 +7,12 @@ import {
|
|||||||
makeFilesServiceMock,
|
makeFilesServiceMock,
|
||||||
} from './test/files.service.mock';
|
} from './test/files.service.mock';
|
||||||
import { DataSource } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
import { createTask, makeTestingModuleWithBlob } from './test/utility';
|
import {
|
||||||
|
createTask,
|
||||||
|
createUserGroupAndMember,
|
||||||
|
getTaskFromJobNumber,
|
||||||
|
makeTestingModuleWithBlobAndNotification,
|
||||||
|
} from './test/utility';
|
||||||
import { FilesService } from './files.service';
|
import { FilesService } from './files.service';
|
||||||
import { makeContext } from '../../common/log';
|
import { makeContext } from '../../common/log';
|
||||||
import {
|
import {
|
||||||
@ -22,6 +27,16 @@ import {
|
|||||||
getTemplateFiles,
|
getTemplateFiles,
|
||||||
} from '../templates/test/utility';
|
} from '../templates/test/utility';
|
||||||
import { TemplateFilesRepositoryService } from '../../repositories/template_files/template_files.repository.service';
|
import { TemplateFilesRepositoryService } from '../../repositories/template_files/template_files.repository.service';
|
||||||
|
import { makeDefaultNotificationhubServiceMockValue } from '../tasks/test/tasks.service.mock';
|
||||||
|
import {
|
||||||
|
createWorkflow,
|
||||||
|
createWorkflowTypist,
|
||||||
|
} from '../workflows/test/utility';
|
||||||
|
import { createWorktype } from '../accounts/test/utility';
|
||||||
|
import { TasksRepositoryService } from '../../repositories/tasks/tasks.repository.service';
|
||||||
|
import { NotificationhubService } from '../../gateways/notificationhub/notificationhub.service';
|
||||||
|
import { makeNotifyMessage } from '../../common/notify/makeNotifyMessage';
|
||||||
|
import { getCheckoutPermissions, getTask } from '../tasks/test/utility';
|
||||||
|
|
||||||
describe('音声ファイルアップロードURL取得', () => {
|
describe('音声ファイルアップロードURL取得', () => {
|
||||||
it('アップロードSASトークンが乗っているURLを返却する', async () => {
|
it('アップロードSASトークンが乗っているURLを返却する', async () => {
|
||||||
@ -109,55 +124,418 @@ describe('音声ファイルアップロードURL取得', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('タスク作成', () => {
|
describe('タスク作成から自動ルーティング(DB使用)', () => {
|
||||||
it('文字起こしタスクを作成できる', async () => {
|
let source: DataSource | null = null;
|
||||||
const blobParam = makeBlobstorageServiceMockValue();
|
beforeEach(async () => {
|
||||||
const userRepoParam = makeDefaultUsersRepositoryMockValue();
|
source = new DataSource({
|
||||||
const taskRepoParam = makeDefaultTasksRepositoryMockValue();
|
type: 'sqlite',
|
||||||
const service = await makeFilesServiceMock(
|
database: ':memory:',
|
||||||
blobParam,
|
logging: false,
|
||||||
userRepoParam,
|
entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
|
||||||
taskRepoParam,
|
synchronize: true, // trueにすると自動的にmigrationが行われるため注意
|
||||||
);
|
});
|
||||||
|
return source.initialize();
|
||||||
expect(
|
|
||||||
await service.uploadFinished(
|
|
||||||
makeContext('trackingId'),
|
|
||||||
'userId',
|
|
||||||
'http://blob/url/file.zip',
|
|
||||||
'AUTHOR_01',
|
|
||||||
'file.zip',
|
|
||||||
'11:22:33',
|
|
||||||
'2023-05-26T11:22:33.444',
|
|
||||||
'2023-05-26T11:22:33.444',
|
|
||||||
'2023-05-26T11:22:33.444',
|
|
||||||
256,
|
|
||||||
'01',
|
|
||||||
'DS2',
|
|
||||||
'comment',
|
|
||||||
'workTypeID',
|
|
||||||
optionItemList,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
).toEqual({ jobNumber: '00000001' });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('日付フォーマットが不正な場合、エラーを返却する', async () => {
|
afterEach(async () => {
|
||||||
const blobParam = makeBlobstorageServiceMockValue();
|
if (!source) return;
|
||||||
const userRepoParam = makeDefaultUsersRepositoryMockValue();
|
await source.destroy();
|
||||||
const taskRepoParam = makeDefaultTasksRepositoryMockValue();
|
source = null;
|
||||||
const service = await makeFilesServiceMock(
|
});
|
||||||
blobParam,
|
it('タスク作成時に、自動ルーティングを行うことができる(APIの引数として渡されたAuthorIDとworkType)', async () => {
|
||||||
userRepoParam,
|
if (!source) fail();
|
||||||
taskRepoParam,
|
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||||
|
const {
|
||||||
|
external_id: authorExternalId,
|
||||||
|
id: authorUserId,
|
||||||
|
author_id: authorAuthorId,
|
||||||
|
} = await makeTestUser(source, {
|
||||||
|
account_id: accountId,
|
||||||
|
external_id: 'author-user-external-id',
|
||||||
|
role: 'author',
|
||||||
|
author_id: 'AUTHOR_ID',
|
||||||
|
});
|
||||||
|
const { id: typistUserId } = await makeTestUser(source, {
|
||||||
|
account_id: accountId,
|
||||||
|
external_id: 'typist-user-external-id',
|
||||||
|
role: 'typist',
|
||||||
|
author_id: undefined,
|
||||||
|
});
|
||||||
|
// ワークタイプを作成
|
||||||
|
const { id: worktypeId, custom_worktype_id } = await createWorktype(
|
||||||
|
source,
|
||||||
|
accountId,
|
||||||
|
'worktypeId',
|
||||||
);
|
);
|
||||||
|
// テンプレートファイルを作成
|
||||||
|
const { id: templateFileId } = await createTemplateFile(
|
||||||
|
source,
|
||||||
|
accountId,
|
||||||
|
'templateFile',
|
||||||
|
'http://blob/url/templateFile.zip',
|
||||||
|
);
|
||||||
|
// ワークフローを作成
|
||||||
|
const { id: workflowId } = await createWorkflow(
|
||||||
|
source,
|
||||||
|
accountId,
|
||||||
|
authorUserId,
|
||||||
|
worktypeId,
|
||||||
|
templateFileId,
|
||||||
|
);
|
||||||
|
// ワークフロータイピストを作成
|
||||||
|
await createWorkflowTypist(source, workflowId, typistUserId);
|
||||||
|
const blobParam = makeBlobstorageServiceMockValue();
|
||||||
|
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||||
|
|
||||||
|
const module = await makeTestingModuleWithBlobAndNotification(
|
||||||
|
source,
|
||||||
|
blobParam,
|
||||||
|
notificationParam,
|
||||||
|
);
|
||||||
|
if (!module) fail();
|
||||||
|
const service = module.get<FilesService>(FilesService);
|
||||||
|
const NotificationHubService = module.get<NotificationhubService>(
|
||||||
|
NotificationhubService,
|
||||||
|
);
|
||||||
|
const result = await service.uploadFinished(
|
||||||
|
makeContext('trackingId'),
|
||||||
|
authorExternalId,
|
||||||
|
'http://blob/url/file.zip',
|
||||||
|
authorAuthorId ?? '',
|
||||||
|
'file.zip',
|
||||||
|
'11:22:33',
|
||||||
|
'2023-05-26T11:22:33.444',
|
||||||
|
'2023-05-26T11:22:33.444',
|
||||||
|
'2023-05-26T11:22:33.444',
|
||||||
|
256,
|
||||||
|
'01',
|
||||||
|
'DS2',
|
||||||
|
'comment',
|
||||||
|
custom_worktype_id,
|
||||||
|
optionItemList,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
expect(result).toEqual({ jobNumber: '00000001' });
|
||||||
|
// 通知処理が想定通りの引数で呼ばれているか確認
|
||||||
|
expect(NotificationHubService.notify).toHaveBeenCalledWith(
|
||||||
|
makeContext('trackingId'),
|
||||||
|
[`user_${typistUserId}`],
|
||||||
|
makeNotifyMessage('M000101'),
|
||||||
|
);
|
||||||
|
// 作成したタスクを取得
|
||||||
|
const resultTask = await getTaskFromJobNumber(source, result.jobNumber);
|
||||||
|
// タスクのチェックアウト権限を取得
|
||||||
|
const resultCheckoutPermission = await getCheckoutPermissions(
|
||||||
|
source,
|
||||||
|
resultTask?.id ?? 0,
|
||||||
|
);
|
||||||
|
// タスクのテンプレートファイルIDを確認
|
||||||
|
expect(resultTask?.template_file_id).toEqual(templateFileId);
|
||||||
|
// タスクのチェックアウト権限が想定通り(ワークフローで設定されている)のユーザーIDで作成されているか確認
|
||||||
|
expect(resultCheckoutPermission.length).toEqual(1);
|
||||||
|
expect(resultCheckoutPermission[0].user_id).toEqual(typistUserId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('別のタスクが既に存在する場合、タスク作成時に、自動ルーティングを行うことができる(APIの引数として渡されたAuthorIDとworkType)', async () => {
|
||||||
|
if (!source) fail();
|
||||||
|
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||||
|
const {
|
||||||
|
external_id: authorExternalId,
|
||||||
|
id: authorUserId,
|
||||||
|
author_id: authorAuthorId,
|
||||||
|
} = await makeTestUser(source, {
|
||||||
|
account_id: accountId,
|
||||||
|
external_id: 'author-user-external-id',
|
||||||
|
role: 'author',
|
||||||
|
author_id: 'AUTHOR_ID',
|
||||||
|
});
|
||||||
|
const { id: typistUserId } = await makeTestUser(source, {
|
||||||
|
account_id: accountId,
|
||||||
|
external_id: 'typist-user-external-id',
|
||||||
|
role: 'typist',
|
||||||
|
author_id: undefined,
|
||||||
|
});
|
||||||
|
//タスクを作成
|
||||||
|
await createTask(
|
||||||
|
source,
|
||||||
|
accountId,
|
||||||
|
'http://blob/url/file.zip',
|
||||||
|
'file.zip',
|
||||||
|
'01',
|
||||||
|
typistUserId,
|
||||||
|
authorAuthorId ?? '',
|
||||||
|
);
|
||||||
|
// ワークタイプを作成
|
||||||
|
const { id: worktypeId, custom_worktype_id } = await createWorktype(
|
||||||
|
source,
|
||||||
|
accountId,
|
||||||
|
'worktypeId',
|
||||||
|
);
|
||||||
|
// ワークフローを作成
|
||||||
|
const { id: workflowId } = await createWorkflow(
|
||||||
|
source,
|
||||||
|
accountId,
|
||||||
|
authorUserId,
|
||||||
|
worktypeId,
|
||||||
|
);
|
||||||
|
// ワークフロータイピストを作成
|
||||||
|
await createWorkflowTypist(source, workflowId, typistUserId);
|
||||||
|
const blobParam = makeBlobstorageServiceMockValue();
|
||||||
|
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||||
|
|
||||||
|
const module = await makeTestingModuleWithBlobAndNotification(
|
||||||
|
source,
|
||||||
|
blobParam,
|
||||||
|
notificationParam,
|
||||||
|
);
|
||||||
|
if (!module) fail();
|
||||||
|
const service = module.get<FilesService>(FilesService);
|
||||||
|
const NotificationHubService = module.get<NotificationhubService>(
|
||||||
|
NotificationhubService,
|
||||||
|
);
|
||||||
|
const result = await service.uploadFinished(
|
||||||
|
makeContext('trackingId'),
|
||||||
|
authorExternalId,
|
||||||
|
'http://blob/url/file.zip',
|
||||||
|
authorAuthorId ?? '',
|
||||||
|
'file.zip',
|
||||||
|
'11:22:33',
|
||||||
|
'2023-05-26T11:22:33.444',
|
||||||
|
'2023-05-26T11:22:33.444',
|
||||||
|
'2023-05-26T11:22:33.444',
|
||||||
|
256,
|
||||||
|
'01',
|
||||||
|
'DS2',
|
||||||
|
'comment',
|
||||||
|
custom_worktype_id,
|
||||||
|
optionItemList,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
expect(result).toEqual({ jobNumber: '00000002' });
|
||||||
|
// 通知処理が想定通りの引数で呼ばれているか確認
|
||||||
|
expect(NotificationHubService.notify).toHaveBeenCalledWith(
|
||||||
|
makeContext('trackingId'),
|
||||||
|
[`user_${typistUserId}`],
|
||||||
|
makeNotifyMessage('M000101'),
|
||||||
|
);
|
||||||
|
// 作成したタスクを取得
|
||||||
|
const resultTask = await getTaskFromJobNumber(source, result.jobNumber);
|
||||||
|
// タスクのチェックアウト権限を取得
|
||||||
|
const resultCheckoutPermission = await getCheckoutPermissions(
|
||||||
|
source,
|
||||||
|
resultTask?.id ?? 0,
|
||||||
|
);
|
||||||
|
// タスクのテンプレートファイルIDを確認
|
||||||
|
expect(resultTask?.template_file_id).toBeNull();
|
||||||
|
// タスクのチェックアウト権限が想定通り(ワークフローで設定されている)のユーザーIDで作成されているか確認
|
||||||
|
expect(resultCheckoutPermission.length).toEqual(1);
|
||||||
|
expect(resultCheckoutPermission[0].user_id).toEqual(typistUserId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('タスク作成時に、自動ルーティングを行うことができる(API実行者のAuthorIDとworkType)', async () => {
|
||||||
|
if (!source) fail();
|
||||||
|
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||||
|
// 音声ファイルの録音者のユーザー
|
||||||
|
const { author_id: authorAuthorId } = await makeTestUser(source, {
|
||||||
|
account_id: accountId,
|
||||||
|
external_id: 'author-user-external-id',
|
||||||
|
role: 'author',
|
||||||
|
author_id: 'AUTHOR_ID',
|
||||||
|
});
|
||||||
|
// ルーティング先のタイピストのユーザー
|
||||||
|
const { id: typistUserId } = await makeTestUser(source, {
|
||||||
|
account_id: accountId,
|
||||||
|
external_id: 'typist-user-external-id',
|
||||||
|
role: 'typist',
|
||||||
|
author_id: undefined,
|
||||||
|
});
|
||||||
|
// API実行者のユーザー
|
||||||
|
const { external_id: myExternalId, id: myUserId } = await makeTestUser(
|
||||||
|
source,
|
||||||
|
{
|
||||||
|
account_id: accountId,
|
||||||
|
external_id: 'my-author-user-external-id',
|
||||||
|
role: 'author',
|
||||||
|
author_id: 'MY_AUTHOR_ID',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// ワークタイプを作成
|
||||||
|
const { id: worktypeId, custom_worktype_id } = await createWorktype(
|
||||||
|
source,
|
||||||
|
accountId,
|
||||||
|
'worktypeId',
|
||||||
|
);
|
||||||
|
|
||||||
|
// テンプレートファイルを作成
|
||||||
|
const { id: templateFileId } = await createTemplateFile(
|
||||||
|
source,
|
||||||
|
accountId,
|
||||||
|
'templateFile',
|
||||||
|
'http://blob/url/templateFile.zip',
|
||||||
|
);
|
||||||
|
|
||||||
|
// ワークフローを作成
|
||||||
|
const { id: workflowId } = await createWorkflow(
|
||||||
|
source,
|
||||||
|
accountId,
|
||||||
|
myUserId, // API実行者のユーザーIDを設定
|
||||||
|
worktypeId,
|
||||||
|
templateFileId,
|
||||||
|
);
|
||||||
|
// ユーザーグループを作成
|
||||||
|
const { userGroupId } = await createUserGroupAndMember(
|
||||||
|
source,
|
||||||
|
accountId,
|
||||||
|
'userGroupName',
|
||||||
|
typistUserId, // ルーティング先のタイピストのユーザーIDを設定
|
||||||
|
);
|
||||||
|
// ワークフロータイピストを作成
|
||||||
|
await createWorkflowTypist(
|
||||||
|
source,
|
||||||
|
workflowId,
|
||||||
|
undefined,
|
||||||
|
userGroupId, // ルーティング先のユーザーグループIDを設定
|
||||||
|
);
|
||||||
|
|
||||||
|
const blobParam = makeBlobstorageServiceMockValue();
|
||||||
|
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||||
|
|
||||||
|
const module = await makeTestingModuleWithBlobAndNotification(
|
||||||
|
source,
|
||||||
|
blobParam,
|
||||||
|
notificationParam,
|
||||||
|
);
|
||||||
|
if (!module) fail();
|
||||||
|
const service = module.get<FilesService>(FilesService);
|
||||||
|
const NotificationHubService = module.get<NotificationhubService>(
|
||||||
|
NotificationhubService,
|
||||||
|
);
|
||||||
|
const result = await service.uploadFinished(
|
||||||
|
makeContext('trackingId'),
|
||||||
|
myExternalId, // API実行者のユーザーIDを設定
|
||||||
|
'http://blob/url/file.zip',
|
||||||
|
authorAuthorId ?? '', // 音声ファイルの情報には、録音者のAuthorIDが入る
|
||||||
|
'file.zip',
|
||||||
|
'11:22:33',
|
||||||
|
'2023-05-26T11:22:33.444',
|
||||||
|
'2023-05-26T11:22:33.444',
|
||||||
|
'2023-05-26T11:22:33.444',
|
||||||
|
256,
|
||||||
|
'01',
|
||||||
|
'DS2',
|
||||||
|
'comment',
|
||||||
|
custom_worktype_id,
|
||||||
|
optionItemList,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
expect(result).toEqual({ jobNumber: '00000001' });
|
||||||
|
// 通知処理が想定通りの引数で呼ばれているか確認
|
||||||
|
expect(NotificationHubService.notify).toHaveBeenCalledWith(
|
||||||
|
makeContext('trackingId'),
|
||||||
|
[`user_${typistUserId}`],
|
||||||
|
makeNotifyMessage('M000101'),
|
||||||
|
);
|
||||||
|
// 作成したタスクを取得
|
||||||
|
const resultTask = await getTaskFromJobNumber(source, result.jobNumber);
|
||||||
|
// タスクのチェックアウト権限を取得
|
||||||
|
const resultCheckoutPermission = await getCheckoutPermissions(
|
||||||
|
source,
|
||||||
|
resultTask?.id ?? 0,
|
||||||
|
);
|
||||||
|
// タスクのテンプレートファイルIDを確認
|
||||||
|
expect(resultTask?.template_file_id).toEqual(templateFileId);
|
||||||
|
// タスクのチェックアウト権限が想定通り(ワークフローで設定されている)のユーザーIDで作成されているか確認
|
||||||
|
expect(resultCheckoutPermission.length).toEqual(1);
|
||||||
|
expect(resultCheckoutPermission[0].user_group_id).toEqual(userGroupId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ワークフローが見つからない場合、タスク作成時に、自動ルーティングを行うことができない', async () => {
|
||||||
|
if (!source) fail();
|
||||||
|
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||||
|
// 音声ファイルの録音者のユーザー
|
||||||
|
const {
|
||||||
|
external_id: authorExternalId,
|
||||||
|
id: authorUserId,
|
||||||
|
author_id: authorAuthorId,
|
||||||
|
} = await makeTestUser(source, {
|
||||||
|
account_id: accountId,
|
||||||
|
external_id: 'author-user-external-id',
|
||||||
|
role: 'author',
|
||||||
|
author_id: 'AUTHOR_ID',
|
||||||
|
});
|
||||||
|
const blobParam = makeBlobstorageServiceMockValue();
|
||||||
|
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||||
|
|
||||||
|
const module = await makeTestingModuleWithBlobAndNotification(
|
||||||
|
source,
|
||||||
|
blobParam,
|
||||||
|
notificationParam,
|
||||||
|
);
|
||||||
|
if (!module) fail();
|
||||||
|
const service = module.get<FilesService>(FilesService);
|
||||||
|
|
||||||
|
const result = await service.uploadFinished(
|
||||||
|
makeContext('trackingId'),
|
||||||
|
authorExternalId, // API実行者のユーザーIDを設定
|
||||||
|
'http://blob/url/file.zip',
|
||||||
|
authorAuthorId ?? '', // 音声ファイルの情報には、録音者のAuthorIDが入る
|
||||||
|
'file.zip',
|
||||||
|
'11:22:33',
|
||||||
|
'2023-05-26T11:22:33.444',
|
||||||
|
'2023-05-26T11:22:33.444',
|
||||||
|
'2023-05-26T11:22:33.444',
|
||||||
|
256,
|
||||||
|
'01',
|
||||||
|
'DS2',
|
||||||
|
'comment',
|
||||||
|
'worktypeId',
|
||||||
|
optionItemList,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
expect(result).toEqual({ jobNumber: '00000001' });
|
||||||
|
// タスクを取得
|
||||||
|
const resultTask = await getTaskFromJobNumber(source, result.jobNumber);
|
||||||
|
// タスクのチェックアウト権限を取得
|
||||||
|
const resultCheckoutPermission = await getCheckoutPermissions(
|
||||||
|
source,
|
||||||
|
resultTask?.id ?? 0,
|
||||||
|
);
|
||||||
|
// タスクがあることを確認
|
||||||
|
expect(resultTask).not.toBeNull();
|
||||||
|
// 自動ルーティングが行われていないことを確認
|
||||||
|
expect(resultCheckoutPermission.length).toEqual(0);
|
||||||
|
});
|
||||||
|
it('日付フォーマットが不正な場合、エラーを返却する', async () => {
|
||||||
|
if (!source) fail();
|
||||||
|
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||||
|
const {
|
||||||
|
external_id: authorExternalId,
|
||||||
|
id: authorUserId,
|
||||||
|
author_id: authorAuthorId,
|
||||||
|
} = await makeTestUser(source, {
|
||||||
|
account_id: accountId,
|
||||||
|
external_id: 'author-user-external-id',
|
||||||
|
role: 'author',
|
||||||
|
author_id: 'AUTHOR_ID',
|
||||||
|
});
|
||||||
|
const blobParam = makeBlobstorageServiceMockValue();
|
||||||
|
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||||
|
|
||||||
|
const module = await makeTestingModuleWithBlobAndNotification(
|
||||||
|
source,
|
||||||
|
blobParam,
|
||||||
|
notificationParam,
|
||||||
|
);
|
||||||
|
if (!module) fail();
|
||||||
|
const service = module.get<FilesService>(FilesService);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
service.uploadFinished(
|
service.uploadFinished(
|
||||||
makeContext('trackingId'),
|
makeContext('trackingId'),
|
||||||
'userId',
|
authorExternalId,
|
||||||
'http://blob/url/file.zip',
|
'http://blob/url/file.zip',
|
||||||
'AUTHOR_01',
|
authorAuthorId ?? '',
|
||||||
'file.zip',
|
'file.zip',
|
||||||
'11:22:33',
|
'11:22:33',
|
||||||
'yyyy-05-26T11:22:33.444',
|
'yyyy-05-26T11:22:33.444',
|
||||||
@ -175,23 +553,36 @@ describe('タスク作成', () => {
|
|||||||
new HttpException(makeErrorResponse('E010001'), HttpStatus.BAD_REQUEST),
|
new HttpException(makeErrorResponse('E010001'), HttpStatus.BAD_REQUEST),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('オプションアイテムが10個ない場合、エラーを返却する', async () => {
|
it('オプションアイテムが10個ない場合、エラーを返却する', async () => {
|
||||||
|
if (!source) fail();
|
||||||
|
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||||
|
const {
|
||||||
|
external_id: authorExternalId,
|
||||||
|
id: authorUserId,
|
||||||
|
author_id: authorAuthorId,
|
||||||
|
} = await makeTestUser(source, {
|
||||||
|
account_id: accountId,
|
||||||
|
external_id: 'author-user-external-id',
|
||||||
|
role: 'author',
|
||||||
|
author_id: 'AUTHOR_ID',
|
||||||
|
});
|
||||||
const blobParam = makeBlobstorageServiceMockValue();
|
const blobParam = makeBlobstorageServiceMockValue();
|
||||||
const userRepoParam = makeDefaultUsersRepositoryMockValue();
|
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||||
const taskRepoParam = makeDefaultTasksRepositoryMockValue();
|
|
||||||
const service = await makeFilesServiceMock(
|
const module = await makeTestingModuleWithBlobAndNotification(
|
||||||
|
source,
|
||||||
blobParam,
|
blobParam,
|
||||||
userRepoParam,
|
notificationParam,
|
||||||
taskRepoParam,
|
|
||||||
);
|
);
|
||||||
|
if (!module) fail();
|
||||||
|
const service = module.get<FilesService>(FilesService);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
service.uploadFinished(
|
service.uploadFinished(
|
||||||
makeContext('trackingId'),
|
makeContext('trackingId'),
|
||||||
'userId',
|
authorExternalId,
|
||||||
'http://blob/url/file.zip',
|
'http://blob/url/file.zip',
|
||||||
'AUTHOR_01',
|
authorAuthorId ?? '',
|
||||||
'file.zip',
|
'file.zip',
|
||||||
'11:22:33',
|
'11:22:33',
|
||||||
'2023-05-26T11:22:33.444',
|
'2023-05-26T11:22:33.444',
|
||||||
@ -214,25 +605,25 @@ describe('タスク作成', () => {
|
|||||||
new HttpException(makeErrorResponse('E010001'), HttpStatus.BAD_REQUEST),
|
new HttpException(makeErrorResponse('E010001'), HttpStatus.BAD_REQUEST),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('タスク追加でユーザー情報の取得に失敗した場合、エラーを返却する', async () => {
|
it('タスク追加でユーザー情報の取得に失敗した場合、エラーを返却する', async () => {
|
||||||
|
if (!source) fail();
|
||||||
const blobParam = makeBlobstorageServiceMockValue();
|
const blobParam = makeBlobstorageServiceMockValue();
|
||||||
const taskRepoParam = makeDefaultTasksRepositoryMockValue();
|
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||||
|
|
||||||
const service = await makeFilesServiceMock(
|
const module = await makeTestingModuleWithBlobAndNotification(
|
||||||
|
source,
|
||||||
blobParam,
|
blobParam,
|
||||||
{
|
notificationParam,
|
||||||
findUserByExternalId: new Error(''),
|
|
||||||
},
|
|
||||||
taskRepoParam,
|
|
||||||
);
|
);
|
||||||
|
if (!module) fail();
|
||||||
|
const service = module.get<FilesService>(FilesService);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
service.uploadFinished(
|
service.uploadFinished(
|
||||||
makeContext('trackingId'),
|
makeContext('trackingId'),
|
||||||
'userId',
|
'authorExternalId',
|
||||||
'http://blob/url/file.zip',
|
'http://blob/url/file.zip',
|
||||||
'AUTHOR_01',
|
'authorAuthorId',
|
||||||
'file.zip',
|
'file.zip',
|
||||||
'11:22:33',
|
'11:22:33',
|
||||||
'2023-05-26T11:22:33.444',
|
'2023-05-26T11:22:33.444',
|
||||||
@ -253,21 +644,37 @@ describe('タスク作成', () => {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('タスクのDBへの追加に失敗した場合、エラーを返却する', async () => {
|
it('タスクのDBへの追加に失敗した場合、エラーを返却する', async () => {
|
||||||
|
if (!source) fail();
|
||||||
const blobParam = makeBlobstorageServiceMockValue();
|
const blobParam = makeBlobstorageServiceMockValue();
|
||||||
const userRepoParam = makeDefaultUsersRepositoryMockValue();
|
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||||
const service = await makeFilesServiceMock(blobParam, userRepoParam, {
|
|
||||||
create: new Error(''),
|
const module = await makeTestingModuleWithBlobAndNotification(
|
||||||
getTasksFromAccountId: new Error(),
|
source,
|
||||||
});
|
blobParam,
|
||||||
|
notificationParam,
|
||||||
|
);
|
||||||
|
if (!module) fail();
|
||||||
|
const service = module.get<FilesService>(FilesService);
|
||||||
|
const taskRepoService = module.get<TasksRepositoryService>(
|
||||||
|
TasksRepositoryService,
|
||||||
|
);
|
||||||
|
taskRepoService.create = jest.fn().mockRejectedValue(new Error(''));
|
||||||
|
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||||
|
const { external_id: authorExternalId, author_id: authorAuthorId } =
|
||||||
|
await makeTestUser(source, {
|
||||||
|
account_id: accountId,
|
||||||
|
external_id: 'author-user-external-id',
|
||||||
|
role: 'author',
|
||||||
|
author_id: 'AUTHOR_ID',
|
||||||
|
});
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
service.uploadFinished(
|
service.uploadFinished(
|
||||||
makeContext('trackingId'),
|
makeContext('trackingId'),
|
||||||
'userId',
|
authorExternalId,
|
||||||
'http://blob/url/file.zip',
|
'http://blob/url/file.zip',
|
||||||
'AUTHOR_01',
|
authorAuthorId ?? '',
|
||||||
'file.zip',
|
'file.zip',
|
||||||
'11:22:33',
|
'11:22:33',
|
||||||
'2023-05-26T11:22:33.444',
|
'2023-05-26T11:22:33.444',
|
||||||
@ -338,7 +745,12 @@ describe('音声ファイルダウンロードURL取得', () => {
|
|||||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||||
blobParam.fileExists = true;
|
blobParam.fileExists = true;
|
||||||
|
|
||||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||||
|
const module = await makeTestingModuleWithBlobAndNotification(
|
||||||
|
source,
|
||||||
|
blobParam,
|
||||||
|
notificationParam,
|
||||||
|
);
|
||||||
if (!module) fail();
|
if (!module) fail();
|
||||||
const service = module.get<FilesService>(FilesService);
|
const service = module.get<FilesService>(FilesService);
|
||||||
|
|
||||||
@ -381,7 +793,12 @@ describe('音声ファイルダウンロードURL取得', () => {
|
|||||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||||
blobParam.fileExists = true;
|
blobParam.fileExists = true;
|
||||||
|
|
||||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||||
|
const module = await makeTestingModuleWithBlobAndNotification(
|
||||||
|
source,
|
||||||
|
blobParam,
|
||||||
|
notificationParam,
|
||||||
|
);
|
||||||
if (!module) fail();
|
if (!module) fail();
|
||||||
const service = module.get<FilesService>(FilesService);
|
const service = module.get<FilesService>(FilesService);
|
||||||
|
|
||||||
@ -430,7 +847,12 @@ describe('音声ファイルダウンロードURL取得', () => {
|
|||||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||||
blobParam.fileExists = true;
|
blobParam.fileExists = true;
|
||||||
|
|
||||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||||
|
const module = await makeTestingModuleWithBlobAndNotification(
|
||||||
|
source,
|
||||||
|
blobParam,
|
||||||
|
notificationParam,
|
||||||
|
);
|
||||||
if (!module) fail();
|
if (!module) fail();
|
||||||
const service = module.get<FilesService>(FilesService);
|
const service = module.get<FilesService>(FilesService);
|
||||||
|
|
||||||
@ -470,7 +892,12 @@ describe('音声ファイルダウンロードURL取得', () => {
|
|||||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||||
blobParam.fileExists = true;
|
blobParam.fileExists = true;
|
||||||
|
|
||||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||||
|
const module = await makeTestingModuleWithBlobAndNotification(
|
||||||
|
source,
|
||||||
|
blobParam,
|
||||||
|
notificationParam,
|
||||||
|
);
|
||||||
if (!module) fail();
|
if (!module) fail();
|
||||||
const service = module.get<FilesService>(FilesService);
|
const service = module.get<FilesService>(FilesService);
|
||||||
|
|
||||||
@ -497,7 +924,12 @@ describe('音声ファイルダウンロードURL取得', () => {
|
|||||||
|
|
||||||
const blobParam = makeBlobstorageServiceMockValue();
|
const blobParam = makeBlobstorageServiceMockValue();
|
||||||
|
|
||||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||||
|
const module = await makeTestingModuleWithBlobAndNotification(
|
||||||
|
source,
|
||||||
|
blobParam,
|
||||||
|
notificationParam,
|
||||||
|
);
|
||||||
if (!module) fail();
|
if (!module) fail();
|
||||||
const service = module.get<FilesService>(FilesService);
|
const service = module.get<FilesService>(FilesService);
|
||||||
|
|
||||||
@ -541,7 +973,12 @@ describe('音声ファイルダウンロードURL取得', () => {
|
|||||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||||
blobParam.fileExists = false;
|
blobParam.fileExists = false;
|
||||||
|
|
||||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||||
|
const module = await makeTestingModuleWithBlobAndNotification(
|
||||||
|
source,
|
||||||
|
blobParam,
|
||||||
|
notificationParam,
|
||||||
|
);
|
||||||
if (!module) fail();
|
if (!module) fail();
|
||||||
const service = module.get<FilesService>(FilesService);
|
const service = module.get<FilesService>(FilesService);
|
||||||
|
|
||||||
@ -604,7 +1041,12 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
|||||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||||
blobParam.fileExists = true;
|
blobParam.fileExists = true;
|
||||||
|
|
||||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||||
|
const module = await makeTestingModuleWithBlobAndNotification(
|
||||||
|
source,
|
||||||
|
blobParam,
|
||||||
|
notificationParam,
|
||||||
|
);
|
||||||
if (!module) fail();
|
if (!module) fail();
|
||||||
const service = module.get<FilesService>(FilesService);
|
const service = module.get<FilesService>(FilesService);
|
||||||
|
|
||||||
@ -641,7 +1083,12 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
|||||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||||
blobParam.fileExists = true;
|
blobParam.fileExists = true;
|
||||||
|
|
||||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||||
|
const module = await makeTestingModuleWithBlobAndNotification(
|
||||||
|
source,
|
||||||
|
blobParam,
|
||||||
|
notificationParam,
|
||||||
|
);
|
||||||
if (!module) fail();
|
if (!module) fail();
|
||||||
const service = module.get<FilesService>(FilesService);
|
const service = module.get<FilesService>(FilesService);
|
||||||
|
|
||||||
@ -686,7 +1133,12 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
|||||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||||
blobParam.fileExists = true;
|
blobParam.fileExists = true;
|
||||||
|
|
||||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||||
|
const module = await makeTestingModuleWithBlobAndNotification(
|
||||||
|
source,
|
||||||
|
blobParam,
|
||||||
|
notificationParam,
|
||||||
|
);
|
||||||
if (!module) fail();
|
if (!module) fail();
|
||||||
const service = module.get<FilesService>(FilesService);
|
const service = module.get<FilesService>(FilesService);
|
||||||
|
|
||||||
@ -726,7 +1178,12 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
|||||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||||
blobParam.fileExists = true;
|
blobParam.fileExists = true;
|
||||||
|
|
||||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||||
|
const module = await makeTestingModuleWithBlobAndNotification(
|
||||||
|
source,
|
||||||
|
blobParam,
|
||||||
|
notificationParam,
|
||||||
|
);
|
||||||
if (!module) fail();
|
if (!module) fail();
|
||||||
const service = module.get<FilesService>(FilesService);
|
const service = module.get<FilesService>(FilesService);
|
||||||
|
|
||||||
@ -753,7 +1210,12 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
|||||||
|
|
||||||
const blobParam = makeBlobstorageServiceMockValue();
|
const blobParam = makeBlobstorageServiceMockValue();
|
||||||
|
|
||||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||||
|
const module = await makeTestingModuleWithBlobAndNotification(
|
||||||
|
source,
|
||||||
|
blobParam,
|
||||||
|
notificationParam,
|
||||||
|
);
|
||||||
if (!module) fail();
|
if (!module) fail();
|
||||||
const service = module.get<FilesService>(FilesService);
|
const service = module.get<FilesService>(FilesService);
|
||||||
|
|
||||||
@ -796,7 +1258,12 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
|||||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||||
blobParam.fileExists = false;
|
blobParam.fileExists = false;
|
||||||
|
|
||||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||||
|
const module = await makeTestingModuleWithBlobAndNotification(
|
||||||
|
source,
|
||||||
|
blobParam,
|
||||||
|
notificationParam,
|
||||||
|
);
|
||||||
if (!module) fail();
|
if (!module) fail();
|
||||||
const service = module.get<FilesService>(FilesService);
|
const service = module.get<FilesService>(FilesService);
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,10 @@ import {
|
|||||||
import { Context } from '../../common/log';
|
import { Context } from '../../common/log';
|
||||||
import { TemplateFilesRepositoryService } from '../../repositories/template_files/template_files.repository.service';
|
import { TemplateFilesRepositoryService } from '../../repositories/template_files/template_files.repository.service';
|
||||||
import { AccountNotFoundError } from '../../repositories/accounts/errors/types';
|
import { AccountNotFoundError } from '../../repositories/accounts/errors/types';
|
||||||
|
import { Task } from '../../repositories/tasks/entity/task.entity';
|
||||||
|
import { UserGroupsRepositoryService } from '../../repositories/user_groups/user_groups.repository.service';
|
||||||
|
import { makeNotifyMessage } from '../../common/notify/makeNotifyMessage';
|
||||||
|
import { NotificationhubService } from '../../gateways/notificationhub/notificationhub.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FilesService {
|
export class FilesService {
|
||||||
@ -34,6 +38,8 @@ export class FilesService {
|
|||||||
private readonly tasksRepositoryService: TasksRepositoryService,
|
private readonly tasksRepositoryService: TasksRepositoryService,
|
||||||
private readonly templateFilesRepository: TemplateFilesRepositoryService,
|
private readonly templateFilesRepository: TemplateFilesRepositoryService,
|
||||||
private readonly blobStorageService: BlobstorageService,
|
private readonly blobStorageService: BlobstorageService,
|
||||||
|
private readonly userGroupsRepositoryService: UserGroupsRepositoryService,
|
||||||
|
private readonly notificationhubService: NotificationhubService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -157,7 +163,7 @@ export class FilesService {
|
|||||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
let task: Task;
|
||||||
try {
|
try {
|
||||||
// URLにSASトークンがついている場合は取り除く
|
// URLにSASトークンがついている場合は取り除く
|
||||||
const urlObj = new URL(url);
|
const urlObj = new URL(url);
|
||||||
@ -167,7 +173,7 @@ export class FilesService {
|
|||||||
|
|
||||||
// 文字起こしタスク追加(音声ファイルとオプションアイテムも同時に追加)
|
// 文字起こしタスク追加(音声ファイルとオプションアイテムも同時に追加)
|
||||||
// 追加時に末尾のJOBナンバーにインクリメントする
|
// 追加時に末尾のJOBナンバーにインクリメントする
|
||||||
const task = await this.tasksRepositoryService.create(
|
task = await this.tasksRepositoryService.create(
|
||||||
user.account_id,
|
user.account_id,
|
||||||
user.id,
|
user.id,
|
||||||
priority,
|
priority,
|
||||||
@ -185,13 +191,57 @@ export class FilesService {
|
|||||||
isEncrypted,
|
isEncrypted,
|
||||||
optionItemList,
|
optionItemList,
|
||||||
);
|
);
|
||||||
return { jobNumber: task.job_number };
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.error(`error=${e}`);
|
this.logger.error(`error=${e}`);
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
makeErrorResponse('E009999'),
|
makeErrorResponse('E009999'),
|
||||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// ルーティング設定に従い、チェックアウト権限を付与する
|
||||||
|
const { typistGroupIds, typistIds } =
|
||||||
|
await this.tasksRepositoryService.autoRouting(
|
||||||
|
task.audio_file_id,
|
||||||
|
user.account_id,
|
||||||
|
workType,
|
||||||
|
user.author_id ?? undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
const groupMembers =
|
||||||
|
await this.userGroupsRepositoryService.getGroupMembersFromGroupIds(
|
||||||
|
typistGroupIds,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 重複のない割り当て候補ユーザーID一覧を取得する
|
||||||
|
const distinctUserIds = [
|
||||||
|
...new Set([...typistIds, ...groupMembers.map((x) => x.user_id)]),
|
||||||
|
];
|
||||||
|
|
||||||
|
// 割り当てられたユーザーがいない場合は通知不要
|
||||||
|
if (distinctUserIds.length === 0) {
|
||||||
|
this.logger.log('No user assigned.');
|
||||||
|
return { jobNumber: task.job_number };
|
||||||
|
}
|
||||||
|
|
||||||
|
// タグを生成
|
||||||
|
const tags = distinctUserIds.map((x) => `user_${x}`);
|
||||||
|
this.logger.log(`tags: ${tags}`);
|
||||||
|
|
||||||
|
// タグ対象に通知送信
|
||||||
|
await this.notificationhubService.notify(
|
||||||
|
context,
|
||||||
|
tags,
|
||||||
|
makeNotifyMessage('M000101'),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 追加したタスクのJOBナンバーを返却
|
||||||
|
return { jobNumber: task.job_number };
|
||||||
|
} catch (error) {
|
||||||
|
// 処理の本筋はタスク生成のため自動ルーティングに失敗してもエラーにしない
|
||||||
|
this.logger.error(`Automatic routing or notification failed.`);
|
||||||
|
this.logger.error(`error=${error}`);
|
||||||
|
return { jobNumber: task.job_number };
|
||||||
} finally {
|
} finally {
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`[OUT] [${context.trackingId}] ${this.uploadFinished.name}`,
|
`[OUT] [${context.trackingId}] ${this.uploadFinished.name}`,
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import { FilesService } from '../files.service';
|
|||||||
import { TasksRepositoryService } from '../../../repositories/tasks/tasks.repository.service';
|
import { TasksRepositoryService } from '../../../repositories/tasks/tasks.repository.service';
|
||||||
import { Task } from '../../../repositories/tasks/entity/task.entity';
|
import { Task } from '../../../repositories/tasks/entity/task.entity';
|
||||||
import { TemplateFilesRepositoryService } from '../../../repositories/template_files/template_files.repository.service';
|
import { TemplateFilesRepositoryService } from '../../../repositories/template_files/template_files.repository.service';
|
||||||
|
import { NotificationhubService } from '../../../gateways/notificationhub/notificationhub.service';
|
||||||
|
import { UserGroupsRepositoryService } from '../../../repositories/user_groups/user_groups.repository.service';
|
||||||
|
|
||||||
export type BlobstorageServiceMockValue = {
|
export type BlobstorageServiceMockValue = {
|
||||||
createContainer: void | Error;
|
createContainer: void | Error;
|
||||||
@ -42,6 +44,10 @@ export const makeFilesServiceMock = async (
|
|||||||
return makeTasksRepositoryMock(tasksRepositoryMockValue);
|
return makeTasksRepositoryMock(tasksRepositoryMockValue);
|
||||||
case TemplateFilesRepositoryService:
|
case TemplateFilesRepositoryService:
|
||||||
return {};
|
return {};
|
||||||
|
case NotificationhubService:
|
||||||
|
return {};
|
||||||
|
case UserGroupsRepositoryService:
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.compile();
|
.compile();
|
||||||
@ -186,6 +192,9 @@ export const makeDefaultTasksRepositoryMockValue =
|
|||||||
option_items: null,
|
option_items: null,
|
||||||
template_file: null,
|
template_file: null,
|
||||||
typist_user: null,
|
typist_user: null,
|
||||||
|
created_by: null,
|
||||||
|
updated_by: null,
|
||||||
|
updated_at: new Date(),
|
||||||
},
|
},
|
||||||
getTasksFromAccountId: {
|
getTasksFromAccountId: {
|
||||||
tasks: [],
|
tasks: [],
|
||||||
|
|||||||
@ -37,6 +37,12 @@ import {
|
|||||||
makeBlobstorageServiceMock,
|
makeBlobstorageServiceMock,
|
||||||
} from './files.service.mock';
|
} from './files.service.mock';
|
||||||
import { TemplateFile } from '../../../repositories/template_files/entity/template_file.entity';
|
import { TemplateFile } from '../../../repositories/template_files/entity/template_file.entity';
|
||||||
|
import {
|
||||||
|
NotificationhubServiceMockValue,
|
||||||
|
makeNotificationhubServiceMock,
|
||||||
|
} from '../../tasks/test/tasks.service.mock';
|
||||||
|
import { UserGroup } from '../../../repositories/user_groups/entity/user_group.entity';
|
||||||
|
import { UserGroupMember } from '../../../repositories/user_groups/entity/user_group_member.entity';
|
||||||
|
|
||||||
export const createTask = async (
|
export const createTask = async (
|
||||||
datasource: DataSource,
|
datasource: DataSource,
|
||||||
@ -95,9 +101,51 @@ export const createTask = async (
|
|||||||
return { audioFileId: audioFile.id };
|
return { audioFileId: audioFile.id };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const makeTestingModuleWithBlob = async (
|
export const getTaskFromJobNumber = async (
|
||||||
|
datasource: DataSource,
|
||||||
|
jobNumber: string,
|
||||||
|
): Promise<Task | null> => {
|
||||||
|
const task = await datasource.getRepository(Task).findOne({
|
||||||
|
where: {
|
||||||
|
job_number: jobNumber,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return task;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ユーザーグループとユーザーグループメンバーを作成する
|
||||||
|
export const createUserGroupAndMember = async (
|
||||||
|
datasource: DataSource,
|
||||||
|
accountId: number,
|
||||||
|
name: string,
|
||||||
|
userId: number,
|
||||||
|
): Promise<{ userGroupId: number }> => {
|
||||||
|
const { identifiers } = await datasource.getRepository(UserGroup).insert({
|
||||||
|
account_id: accountId,
|
||||||
|
name: name,
|
||||||
|
deleted_at: null,
|
||||||
|
created_by: 'test_runner',
|
||||||
|
created_at: new Date(),
|
||||||
|
updated_by: 'updater',
|
||||||
|
updated_at: new Date(),
|
||||||
|
});
|
||||||
|
const userGroup = identifiers.pop() as UserGroup;
|
||||||
|
// ユーザーグループメンバーを作成する
|
||||||
|
await datasource.getRepository(UserGroupMember).insert({
|
||||||
|
user_group_id: userGroup.id,
|
||||||
|
user_id: userId,
|
||||||
|
created_by: 'test_runner',
|
||||||
|
created_at: new Date(),
|
||||||
|
updated_by: 'updater',
|
||||||
|
updated_at: new Date(),
|
||||||
|
});
|
||||||
|
return { userGroupId: userGroup.id };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const makeTestingModuleWithBlobAndNotification = async (
|
||||||
datasource: DataSource,
|
datasource: DataSource,
|
||||||
blobStorageService: BlobstorageServiceMockValue,
|
blobStorageService: BlobstorageServiceMockValue,
|
||||||
|
notificationhubService: NotificationhubServiceMockValue,
|
||||||
): Promise<TestingModule | undefined> => {
|
): Promise<TestingModule | undefined> => {
|
||||||
try {
|
try {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
@ -148,6 +196,8 @@ export const makeTestingModuleWithBlob = async (
|
|||||||
})
|
})
|
||||||
.overrideProvider(BlobstorageService)
|
.overrideProvider(BlobstorageService)
|
||||||
.useValue(makeBlobstorageServiceMock(blobStorageService))
|
.useValue(makeBlobstorageServiceMock(blobStorageService))
|
||||||
|
.overrideProvider(NotificationhubService)
|
||||||
|
.useValue(makeNotificationhubServiceMock(notificationhubService))
|
||||||
.compile();
|
.compile();
|
||||||
|
|
||||||
return module;
|
return module;
|
||||||
|
|||||||
@ -208,6 +208,9 @@ describe('TasksService', () => {
|
|||||||
template_file_id: null,
|
template_file_id: null,
|
||||||
typist_user: null,
|
typist_user: null,
|
||||||
template_file: null,
|
template_file: null,
|
||||||
|
created_by: null,
|
||||||
|
updated_by: null,
|
||||||
|
updated_at: new Date('2023-01-01T01:01:01.000'),
|
||||||
file: {
|
file: {
|
||||||
id: 1,
|
id: 1,
|
||||||
account_id: 1,
|
account_id: 1,
|
||||||
|
|||||||
@ -362,6 +362,9 @@ const defaultTasksRepositoryMockValue: {
|
|||||||
template_file_id: null,
|
template_file_id: null,
|
||||||
typist_user: null,
|
typist_user: null,
|
||||||
template_file: null,
|
template_file: null,
|
||||||
|
created_by: null,
|
||||||
|
updated_by: null,
|
||||||
|
updated_at: new Date('2023-01-01T01:01:01.000Z'),
|
||||||
option_items: [
|
option_items: [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
|
|||||||
@ -337,7 +337,7 @@ export const makeDefaultAdB2cMockValue = (): AdB2cMockValue => {
|
|||||||
},
|
},
|
||||||
createUser: '001',
|
createUser: '001',
|
||||||
getUser: {
|
getUser: {
|
||||||
id: "xxxx-xxxxx-xxxxx-xxxx",
|
id: 'xxxx-xxxxx-xxxxx-xxxx',
|
||||||
displayName: 'Hanako Sato',
|
displayName: 'Hanako Sato',
|
||||||
},
|
},
|
||||||
getUsers: AdB2cMockUsers,
|
getUsers: AdB2cMockUsers,
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import {
|
|||||||
JoinColumn,
|
JoinColumn,
|
||||||
OneToMany,
|
OneToMany,
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { bigintTransformer } from '../../../common/entity';
|
import { bigintTransformer } from '../../../common/entity';
|
||||||
|
|
||||||
@ -37,8 +39,24 @@ export class Task {
|
|||||||
started_at: Date | null;
|
started_at: Date | null;
|
||||||
@Column({ nullable: true, type: 'datetime' })
|
@Column({ nullable: true, type: 'datetime' })
|
||||||
finished_at: Date | null;
|
finished_at: Date | null;
|
||||||
@Column({})
|
|
||||||
|
@Column({ nullable: true, type: 'datetime' })
|
||||||
|
created_by: string | null;
|
||||||
|
|
||||||
|
@CreateDateColumn({
|
||||||
|
default: () => "datetime('now', 'localtime')",
|
||||||
|
type: 'datetime',
|
||||||
|
}) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
|
|
||||||
|
@Column({ nullable: true, type: 'datetime' })
|
||||||
|
updated_by: string | null;
|
||||||
|
|
||||||
|
@UpdateDateColumn({
|
||||||
|
default: () => "datetime('now', 'localtime')",
|
||||||
|
type: 'datetime',
|
||||||
|
}) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||||
|
updated_at: Date;
|
||||||
@OneToOne(() => AudioFile, (audiofile) => audiofile.task)
|
@OneToOne(() => AudioFile, (audiofile) => audiofile.task)
|
||||||
@JoinColumn({ name: 'audio_file_id' })
|
@JoinColumn({ name: 'audio_file_id' })
|
||||||
file: AudioFile | null;
|
file: AudioFile | null;
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
DataSource,
|
DataSource,
|
||||||
|
EntityManager,
|
||||||
FindOptionsOrder,
|
FindOptionsOrder,
|
||||||
FindOptionsOrderValue,
|
FindOptionsOrderValue,
|
||||||
In,
|
In,
|
||||||
IsNull,
|
IsNull,
|
||||||
|
Repository,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { Task } from './entity/task.entity';
|
import { Task } from './entity/task.entity';
|
||||||
import { ADMIN_ROLES, TASK_STATUS, USER_ROLES } from '../../constants';
|
import { ADMIN_ROLES, TASK_STATUS, USER_ROLES } from '../../constants';
|
||||||
@ -35,6 +37,8 @@ import {
|
|||||||
import { Roles } from '../../common/types/role';
|
import { Roles } from '../../common/types/role';
|
||||||
import { TaskStatus, isTaskStatus } from '../../common/types/taskStatus';
|
import { TaskStatus, isTaskStatus } from '../../common/types/taskStatus';
|
||||||
import { SortCriteria } from '../sort_criteria/entity/sort_criteria.entity';
|
import { SortCriteria } from '../sort_criteria/entity/sort_criteria.entity';
|
||||||
|
import { Workflow } from '../workflows/entity/workflow.entity';
|
||||||
|
import { Worktype } from '../worktypes/entity/worktype.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TasksRepositoryService {
|
export class TasksRepositoryService {
|
||||||
@ -710,18 +714,6 @@ export class TasksRepositoryService {
|
|||||||
|
|
||||||
task.audio_file_id = savedAudioFile.id;
|
task.audio_file_id = savedAudioFile.id;
|
||||||
|
|
||||||
const optionItems = paramOptionItems.map((x) => {
|
|
||||||
return {
|
|
||||||
audio_file_id: savedAudioFile.id,
|
|
||||||
label: x.optionItemLabel,
|
|
||||||
value: x.optionItemValue,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const optionItemRepo = entityManager.getRepository(AudioOptionItem);
|
|
||||||
const newAudioOptionItems = optionItemRepo.create(optionItems);
|
|
||||||
await optionItemRepo.save(newAudioOptionItems);
|
|
||||||
|
|
||||||
const taskRepo = entityManager.getRepository(Task);
|
const taskRepo = entityManager.getRepository(Task);
|
||||||
|
|
||||||
// アカウント内でJOBナンバーが有効なタスクのうち最新のものを取得
|
// アカウント内でJOBナンバーが有効なタスクのうち最新のものを取得
|
||||||
@ -743,8 +735,19 @@ export class TasksRepositoryService {
|
|||||||
}
|
}
|
||||||
task.job_number = newJobNumber;
|
task.job_number = newJobNumber;
|
||||||
|
|
||||||
const newTask = taskRepo.create(task);
|
const persisted = await taskRepo.save(task);
|
||||||
const persisted = await taskRepo.save(newTask);
|
|
||||||
|
const optionItems = paramOptionItems.map((x) => {
|
||||||
|
return {
|
||||||
|
audio_file_id: persisted.audio_file_id,
|
||||||
|
label: x.optionItemLabel,
|
||||||
|
value: x.optionItemValue,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const optionItemRepo = entityManager.getRepository(AudioOptionItem);
|
||||||
|
const newAudioOptionItems = optionItemRepo.create(optionItems);
|
||||||
|
await optionItemRepo.save(newAudioOptionItems);
|
||||||
return persisted;
|
return persisted;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -952,6 +955,231 @@ export class TasksRepositoryService {
|
|||||||
return tasks;
|
return tasks;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* worktypeIdをもとにルーティングルールを取得し、タスクのチェックアウト権限を設定する
|
||||||
|
* @param audioFileId
|
||||||
|
* @param accountId
|
||||||
|
* @param worktypeId
|
||||||
|
* @param [myAuthorId]
|
||||||
|
* @returns typistIds: タイピストIDの一覧 / typistGroupIds: タイピストグループIDの一覧
|
||||||
|
*/
|
||||||
|
async autoRouting(
|
||||||
|
audioFileId: number,
|
||||||
|
accountId: number,
|
||||||
|
worktypeId: string, // ユーザーが任意につけるworktypeId(DBのcustom_worktype_id)
|
||||||
|
myAuthorId?: string, // API実行者のAuthorId
|
||||||
|
): Promise<{ typistIds: number[]; typistGroupIds: number[] }> {
|
||||||
|
return await this.dataSource.transaction(async (entityManager) => {
|
||||||
|
// 音声ファイルを取得
|
||||||
|
const audioFileRepo = entityManager.getRepository(AudioFile);
|
||||||
|
const audioFile = await audioFileRepo.findOne({
|
||||||
|
relations: {
|
||||||
|
task: true,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
id: audioFileId,
|
||||||
|
account_id: accountId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!audioFile) {
|
||||||
|
throw new Error(
|
||||||
|
`audio file not found. audio_file_id:${audioFileId}, accountId:${accountId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { task } = audioFile;
|
||||||
|
|
||||||
|
if (!task) {
|
||||||
|
throw new Error(
|
||||||
|
`task not found. audio_file_id:${audioFileId}, accountId:${accountId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// authorIdをもとにユーザーを取得
|
||||||
|
const userRepo = entityManager.getRepository(User);
|
||||||
|
const authorUser = await userRepo.findOne({
|
||||||
|
where: {
|
||||||
|
author_id: audioFile.author_id,
|
||||||
|
account_id: accountId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!authorUser) {
|
||||||
|
throw new Error(
|
||||||
|
`user not found. authorId:${audioFile.author_id}, accountId:${accountId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// ユーザーが任意につけるworktypeIdをもとにworktypeを取得
|
||||||
|
const worktypeRepo = entityManager.getRepository(Worktype);
|
||||||
|
const worktypeRecord = await worktypeRepo.findOne({
|
||||||
|
where: {
|
||||||
|
custom_worktype_id: worktypeId,
|
||||||
|
account_id: accountId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!worktypeRecord) {
|
||||||
|
throw new Error(
|
||||||
|
`worktype not found. worktype:${worktypeId}, accountId:${accountId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Workflow(ルーティングルール)を取得
|
||||||
|
const workflowRepo = entityManager.getRepository(Workflow);
|
||||||
|
const workflow = await workflowRepo.findOne({
|
||||||
|
relations: {
|
||||||
|
workflowTypists: true,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
account_id: accountId,
|
||||||
|
author_id: authorUser.id,
|
||||||
|
worktype_id: worktypeRecord.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Workflow(ルーティングルール)があればタスクのチェックアウト権限を設定する
|
||||||
|
if (workflow) {
|
||||||
|
return await this.setCheckoutPermissionAndTemplate(
|
||||||
|
workflow,
|
||||||
|
task,
|
||||||
|
accountId,
|
||||||
|
entityManager,
|
||||||
|
userRepo,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 音声ファイルの情報からルーティングルールを取得できない場合は、
|
||||||
|
// API実行者のAuthorIdと音声ファイルのWorktypeをもとにルーティングルールを取得する
|
||||||
|
// API実行者のAuthorIdがない場合はエラーを出して終了
|
||||||
|
if (!myAuthorId) {
|
||||||
|
throw new Error(`There is no AuthorId for the API executor.`);
|
||||||
|
}
|
||||||
|
// API実行者のAuthorIdをもとにユーザーを取得
|
||||||
|
const myAuthorUser = await userRepo.findOne({
|
||||||
|
where: {
|
||||||
|
author_id: myAuthorId,
|
||||||
|
account_id: accountId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!myAuthorUser) {
|
||||||
|
throw new Error(
|
||||||
|
`user not found. authorId:${myAuthorId}, accountId:${accountId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const defaultWorkflow = await workflowRepo.findOne({
|
||||||
|
relations: {
|
||||||
|
workflowTypists: true,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
account_id: accountId,
|
||||||
|
author_id: myAuthorUser.id,
|
||||||
|
worktype_id: worktypeRecord.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// API実行者のAuthorIdと音声ファイルのWorktypeをもとにルーティングルールを取得できない場合はエラーを出して終了
|
||||||
|
if (!defaultWorkflow) {
|
||||||
|
throw new Error(
|
||||||
|
`workflow not found. authorUserId:${myAuthorUser.id}, accountId:${accountId}, worktype:${worktypeId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Workflow(ルーティングルール)があればタスクのチェックアウト権限を設定する
|
||||||
|
return await this.setCheckoutPermissionAndTemplate(
|
||||||
|
defaultWorkflow,
|
||||||
|
task,
|
||||||
|
accountId,
|
||||||
|
entityManager,
|
||||||
|
userRepo,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* workflowに紐づけられているタイピスト・タイピストグループで、タスクのチェックアウト権限を設定
|
||||||
|
* workflowに紐づけられているテンプレートファイルIDをタスクに設定
|
||||||
|
*
|
||||||
|
* @param workflow
|
||||||
|
* @param task
|
||||||
|
* @param accountId
|
||||||
|
* @param entityManager
|
||||||
|
* @param userRepo
|
||||||
|
* @returns checkout permission
|
||||||
|
*/
|
||||||
|
private async setCheckoutPermissionAndTemplate(
|
||||||
|
workflow: Workflow,
|
||||||
|
task: Task,
|
||||||
|
accountId: number,
|
||||||
|
entityManager: EntityManager,
|
||||||
|
userRepo: Repository<User>,
|
||||||
|
): Promise<{ typistIds: number[]; typistGroupIds: number[] }> {
|
||||||
|
const { workflowTypists, template_id } = workflow;
|
||||||
|
if (!workflowTypists) {
|
||||||
|
throw new Error(`workflowTypists not found. workflowId:${workflow.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// タスクのテンプレートIDを更新
|
||||||
|
const taskRepo = entityManager.getRepository(Task);
|
||||||
|
await taskRepo.update(
|
||||||
|
{ id: task.id },
|
||||||
|
{
|
||||||
|
template_file_id: template_id,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// 取得したルーティングルールのタイピストまたはタイピストグループをチェックアウト権限に設定する
|
||||||
|
|
||||||
|
// ルーティング候補ユーザーの存在確認
|
||||||
|
const typistIds = workflowTypists.flatMap((typist) =>
|
||||||
|
typist.typist_id ? [typist.typist_id] : [],
|
||||||
|
);
|
||||||
|
const typistUsers = await userRepo.find({
|
||||||
|
where: { account_id: accountId, id: In(typistIds) },
|
||||||
|
});
|
||||||
|
if (typistUsers.length !== typistIds.length) {
|
||||||
|
throw new Error(`typist not found. ids: ${typistIds}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ルーティング候補ユーザーグループの存在確認
|
||||||
|
const groupIds = workflowTypists.flatMap((typist) => {
|
||||||
|
return typist.typist_group_id ? [typist.typist_group_id] : [];
|
||||||
|
});
|
||||||
|
const userGroupRepo = entityManager.getRepository(UserGroup);
|
||||||
|
const typistGroups = await userGroupRepo.find({
|
||||||
|
where: { account_id: accountId, id: In(groupIds) },
|
||||||
|
});
|
||||||
|
if (typistGroups.length !== groupIds.length) {
|
||||||
|
throw new Error(`typist group not found. ids: ${groupIds}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkoutPermissionRepo =
|
||||||
|
entityManager.getRepository(CheckoutPermission);
|
||||||
|
|
||||||
|
// 当該タスクに紐づく既存checkoutPermissionをdelete
|
||||||
|
await checkoutPermissionRepo.delete({
|
||||||
|
task_id: task.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ルーティング候補ユーザーのチェックアウト権限を作成
|
||||||
|
const typistPermissions = typistUsers.map((typistUser) => {
|
||||||
|
const permission = new CheckoutPermission();
|
||||||
|
permission.task_id = task.id;
|
||||||
|
permission.user_id = typistUser.id;
|
||||||
|
return permission;
|
||||||
|
});
|
||||||
|
// ルーティング候補ユーザーグループのチェックアウト権限を作成
|
||||||
|
const typistGroupPermissions = typistGroups.map((typistGroup) => {
|
||||||
|
const permission = new CheckoutPermission();
|
||||||
|
permission.task_id = task.id;
|
||||||
|
permission.user_group_id = typistGroup.id;
|
||||||
|
return permission;
|
||||||
|
});
|
||||||
|
const permissions = [...typistPermissions, ...typistGroupPermissions];
|
||||||
|
await checkoutPermissionRepo.save(permissions);
|
||||||
|
// user_idsとuser_group_idsを返却する
|
||||||
|
return {
|
||||||
|
typistIds: typistIds,
|
||||||
|
typistGroupIds: groupIds,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ソート用オブジェクトを生成する
|
// ソート用オブジェクトを生成する
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user