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 キャッシュのキー
|
||||
*/
|
||||
export const makeADB2CKey = (externalId: string): string => {
|
||||
return `${ADB2C_PREFIX}${externalId}`;
|
||||
}
|
||||
return `${ADB2C_PREFIX}${externalId}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* ADB2Cのユーザー格納用のキーから外部ユーザーIDを取得する
|
||||
@ -15,5 +15,5 @@ export const makeADB2CKey = (externalId: string): string => {
|
||||
* @returns 外部ユーザーID
|
||||
*/
|
||||
export const restoreAdB2cID = (key: string): string => {
|
||||
return key.replace(ADB2C_PREFIX, '');
|
||||
}
|
||||
return key.replace(ADB2C_PREFIX, '');
|
||||
};
|
||||
|
||||
@ -7,11 +7,5 @@ import type {
|
||||
} from './types';
|
||||
import { isIDToken } from './typeguard';
|
||||
|
||||
export type {
|
||||
AccessToken,
|
||||
B2cMetadata,
|
||||
IDToken,
|
||||
JwkSignKey,
|
||||
RefreshToken,
|
||||
};
|
||||
export type { AccessToken, B2cMetadata, IDToken, JwkSignKey, RefreshToken };
|
||||
export { isIDToken };
|
||||
|
||||
@ -150,8 +150,9 @@ export const createWorktype = async (
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return worktype;
|
||||
return (await datasource
|
||||
.getRepository(Worktype)
|
||||
.findOne({ where: { id: worktype.id } })) as Worktype;
|
||||
};
|
||||
|
||||
// Worktypeを取得する
|
||||
|
||||
@ -7,6 +7,8 @@ import { AudioOptionItemsRepositoryModule } from '../../repositories/audio_optio
|
||||
import { TasksRepositoryModule } from '../../repositories/tasks/tasks.repository.module';
|
||||
import { BlobstorageModule } from '../../gateways/blobstorage/blobstorage.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({
|
||||
imports: [
|
||||
@ -16,6 +18,8 @@ import { TemplateFilesRepositoryModule } from '../../repositories/template_files
|
||||
TasksRepositoryModule,
|
||||
BlobstorageModule,
|
||||
TemplateFilesRepositoryModule,
|
||||
UserGroupsRepositoryModule,
|
||||
NotificationhubModule,
|
||||
],
|
||||
providers: [FilesService],
|
||||
controllers: [FilesController],
|
||||
|
||||
@ -7,7 +7,12 @@ import {
|
||||
makeFilesServiceMock,
|
||||
} from './test/files.service.mock';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { createTask, makeTestingModuleWithBlob } from './test/utility';
|
||||
import {
|
||||
createTask,
|
||||
createUserGroupAndMember,
|
||||
getTaskFromJobNumber,
|
||||
makeTestingModuleWithBlobAndNotification,
|
||||
} from './test/utility';
|
||||
import { FilesService } from './files.service';
|
||||
import { makeContext } from '../../common/log';
|
||||
import {
|
||||
@ -22,6 +27,16 @@ import {
|
||||
getTemplateFiles,
|
||||
} from '../templates/test/utility';
|
||||
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取得', () => {
|
||||
it('アップロードSASトークンが乗っているURLを返却する', async () => {
|
||||
@ -109,55 +124,418 @@ describe('音声ファイルアップロードURL取得', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('タスク作成', () => {
|
||||
it('文字起こしタスクを作成できる', async () => {
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
const userRepoParam = makeDefaultUsersRepositoryMockValue();
|
||||
const taskRepoParam = makeDefaultTasksRepositoryMockValue();
|
||||
const service = await makeFilesServiceMock(
|
||||
blobParam,
|
||||
userRepoParam,
|
||||
taskRepoParam,
|
||||
);
|
||||
|
||||
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' });
|
||||
describe('タスク作成から自動ルーティング(DB使用)', () => {
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
database: ':memory:',
|
||||
logging: false,
|
||||
entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
|
||||
synchronize: true, // trueにすると自動的にmigrationが行われるため注意
|
||||
});
|
||||
return source.initialize();
|
||||
});
|
||||
|
||||
it('日付フォーマットが不正な場合、エラーを返却する', async () => {
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
const userRepoParam = makeDefaultUsersRepositoryMockValue();
|
||||
const taskRepoParam = makeDefaultTasksRepositoryMockValue();
|
||||
const service = await makeFilesServiceMock(
|
||||
blobParam,
|
||||
userRepoParam,
|
||||
taskRepoParam,
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
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,
|
||||
});
|
||||
// ワークタイプを作成
|
||||
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(
|
||||
service.uploadFinished(
|
||||
makeContext('trackingId'),
|
||||
'userId',
|
||||
authorExternalId,
|
||||
'http://blob/url/file.zip',
|
||||
'AUTHOR_01',
|
||||
authorAuthorId ?? '',
|
||||
'file.zip',
|
||||
'11:22:33',
|
||||
'yyyy-05-26T11:22:33.444',
|
||||
@ -175,23 +553,36 @@ describe('タスク作成', () => {
|
||||
new HttpException(makeErrorResponse('E010001'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
|
||||
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 userRepoParam = makeDefaultUsersRepositoryMockValue();
|
||||
const taskRepoParam = makeDefaultTasksRepositoryMockValue();
|
||||
const service = await makeFilesServiceMock(
|
||||
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||
|
||||
const module = await makeTestingModuleWithBlobAndNotification(
|
||||
source,
|
||||
blobParam,
|
||||
userRepoParam,
|
||||
taskRepoParam,
|
||||
notificationParam,
|
||||
);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
service.uploadFinished(
|
||||
makeContext('trackingId'),
|
||||
'userId',
|
||||
authorExternalId,
|
||||
'http://blob/url/file.zip',
|
||||
'AUTHOR_01',
|
||||
authorAuthorId ?? '',
|
||||
'file.zip',
|
||||
'11:22:33',
|
||||
'2023-05-26T11:22:33.444',
|
||||
@ -214,25 +605,25 @@ describe('タスク作成', () => {
|
||||
new HttpException(makeErrorResponse('E010001'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
|
||||
it('タスク追加でユーザー情報の取得に失敗した場合、エラーを返却する', async () => {
|
||||
if (!source) fail();
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
const taskRepoParam = makeDefaultTasksRepositoryMockValue();
|
||||
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||
|
||||
const service = await makeFilesServiceMock(
|
||||
const module = await makeTestingModuleWithBlobAndNotification(
|
||||
source,
|
||||
blobParam,
|
||||
{
|
||||
findUserByExternalId: new Error(''),
|
||||
},
|
||||
taskRepoParam,
|
||||
notificationParam,
|
||||
);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
service.uploadFinished(
|
||||
makeContext('trackingId'),
|
||||
'userId',
|
||||
'authorExternalId',
|
||||
'http://blob/url/file.zip',
|
||||
'AUTHOR_01',
|
||||
'authorAuthorId',
|
||||
'file.zip',
|
||||
'11:22:33',
|
||||
'2023-05-26T11:22:33.444',
|
||||
@ -253,21 +644,37 @@ describe('タスク作成', () => {
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('タスクのDBへの追加に失敗した場合、エラーを返却する', async () => {
|
||||
if (!source) fail();
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
const userRepoParam = makeDefaultUsersRepositoryMockValue();
|
||||
const service = await makeFilesServiceMock(blobParam, userRepoParam, {
|
||||
create: new Error(''),
|
||||
getTasksFromAccountId: new Error(),
|
||||
});
|
||||
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||
|
||||
const module = await makeTestingModuleWithBlobAndNotification(
|
||||
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(
|
||||
service.uploadFinished(
|
||||
makeContext('trackingId'),
|
||||
'userId',
|
||||
authorExternalId,
|
||||
'http://blob/url/file.zip',
|
||||
'AUTHOR_01',
|
||||
authorAuthorId ?? '',
|
||||
'file.zip',
|
||||
'11:22:33',
|
||||
'2023-05-26T11:22:33.444',
|
||||
@ -338,7 +745,12 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlobAndNotification(
|
||||
source,
|
||||
blobParam,
|
||||
notificationParam,
|
||||
);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
@ -381,7 +793,12 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlobAndNotification(
|
||||
source,
|
||||
blobParam,
|
||||
notificationParam,
|
||||
);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
@ -430,7 +847,12 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlobAndNotification(
|
||||
source,
|
||||
blobParam,
|
||||
notificationParam,
|
||||
);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
@ -470,7 +892,12 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlobAndNotification(
|
||||
source,
|
||||
blobParam,
|
||||
notificationParam,
|
||||
);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
@ -497,7 +924,12 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlobAndNotification(
|
||||
source,
|
||||
blobParam,
|
||||
notificationParam,
|
||||
);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
@ -541,7 +973,12 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||
blobParam.fileExists = false;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlobAndNotification(
|
||||
source,
|
||||
blobParam,
|
||||
notificationParam,
|
||||
);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
@ -604,7 +1041,12 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlobAndNotification(
|
||||
source,
|
||||
blobParam,
|
||||
notificationParam,
|
||||
);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
@ -641,7 +1083,12 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlobAndNotification(
|
||||
source,
|
||||
blobParam,
|
||||
notificationParam,
|
||||
);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
@ -686,7 +1133,12 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlobAndNotification(
|
||||
source,
|
||||
blobParam,
|
||||
notificationParam,
|
||||
);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
@ -726,7 +1178,12 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlobAndNotification(
|
||||
source,
|
||||
blobParam,
|
||||
notificationParam,
|
||||
);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
@ -753,7 +1210,12 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlobAndNotification(
|
||||
source,
|
||||
blobParam,
|
||||
notificationParam,
|
||||
);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
@ -796,7 +1258,12 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||
blobParam.fileExists = false;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlobAndNotification(
|
||||
source,
|
||||
blobParam,
|
||||
notificationParam,
|
||||
);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
|
||||
@ -24,6 +24,10 @@ import {
|
||||
import { Context } from '../../common/log';
|
||||
import { TemplateFilesRepositoryService } from '../../repositories/template_files/template_files.repository.service';
|
||||
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()
|
||||
export class FilesService {
|
||||
@ -34,6 +38,8 @@ export class FilesService {
|
||||
private readonly tasksRepositoryService: TasksRepositoryService,
|
||||
private readonly templateFilesRepository: TemplateFilesRepositoryService,
|
||||
private readonly blobStorageService: BlobstorageService,
|
||||
private readonly userGroupsRepositoryService: UserGroupsRepositoryService,
|
||||
private readonly notificationhubService: NotificationhubService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@ -157,7 +163,7 @@ export class FilesService {
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
let task: Task;
|
||||
try {
|
||||
// URLにSASトークンがついている場合は取り除く
|
||||
const urlObj = new URL(url);
|
||||
@ -167,7 +173,7 @@ export class FilesService {
|
||||
|
||||
// 文字起こしタスク追加(音声ファイルとオプションアイテムも同時に追加)
|
||||
// 追加時に末尾のJOBナンバーにインクリメントする
|
||||
const task = await this.tasksRepositoryService.create(
|
||||
task = await this.tasksRepositoryService.create(
|
||||
user.account_id,
|
||||
user.id,
|
||||
priority,
|
||||
@ -185,13 +191,57 @@ export class FilesService {
|
||||
isEncrypted,
|
||||
optionItemList,
|
||||
);
|
||||
return { jobNumber: task.job_number };
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
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 {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.trackingId}] ${this.uploadFinished.name}`,
|
||||
|
||||
@ -6,6 +6,8 @@ import { FilesService } from '../files.service';
|
||||
import { TasksRepositoryService } from '../../../repositories/tasks/tasks.repository.service';
|
||||
import { Task } from '../../../repositories/tasks/entity/task.entity';
|
||||
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 = {
|
||||
createContainer: void | Error;
|
||||
@ -42,6 +44,10 @@ export const makeFilesServiceMock = async (
|
||||
return makeTasksRepositoryMock(tasksRepositoryMockValue);
|
||||
case TemplateFilesRepositoryService:
|
||||
return {};
|
||||
case NotificationhubService:
|
||||
return {};
|
||||
case UserGroupsRepositoryService:
|
||||
return {};
|
||||
}
|
||||
})
|
||||
.compile();
|
||||
@ -186,6 +192,9 @@ export const makeDefaultTasksRepositoryMockValue =
|
||||
option_items: null,
|
||||
template_file: null,
|
||||
typist_user: null,
|
||||
created_by: null,
|
||||
updated_by: null,
|
||||
updated_at: new Date(),
|
||||
},
|
||||
getTasksFromAccountId: {
|
||||
tasks: [],
|
||||
|
||||
@ -37,6 +37,12 @@ import {
|
||||
makeBlobstorageServiceMock,
|
||||
} from './files.service.mock';
|
||||
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 (
|
||||
datasource: DataSource,
|
||||
@ -95,9 +101,51 @@ export const createTask = async (
|
||||
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,
|
||||
blobStorageService: BlobstorageServiceMockValue,
|
||||
notificationhubService: NotificationhubServiceMockValue,
|
||||
): Promise<TestingModule | undefined> => {
|
||||
try {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@ -148,6 +196,8 @@ export const makeTestingModuleWithBlob = async (
|
||||
})
|
||||
.overrideProvider(BlobstorageService)
|
||||
.useValue(makeBlobstorageServiceMock(blobStorageService))
|
||||
.overrideProvider(NotificationhubService)
|
||||
.useValue(makeNotificationhubServiceMock(notificationhubService))
|
||||
.compile();
|
||||
|
||||
return module;
|
||||
|
||||
@ -208,6 +208,9 @@ describe('TasksService', () => {
|
||||
template_file_id: null,
|
||||
typist_user: null,
|
||||
template_file: null,
|
||||
created_by: null,
|
||||
updated_by: null,
|
||||
updated_at: new Date('2023-01-01T01:01:01.000'),
|
||||
file: {
|
||||
id: 1,
|
||||
account_id: 1,
|
||||
|
||||
@ -362,6 +362,9 @@ const defaultTasksRepositoryMockValue: {
|
||||
template_file_id: null,
|
||||
typist_user: null,
|
||||
template_file: null,
|
||||
created_by: null,
|
||||
updated_by: null,
|
||||
updated_at: new Date('2023-01-01T01:01:01.000Z'),
|
||||
option_items: [
|
||||
{
|
||||
id: 1,
|
||||
|
||||
@ -337,7 +337,7 @@ export const makeDefaultAdB2cMockValue = (): AdB2cMockValue => {
|
||||
},
|
||||
createUser: '001',
|
||||
getUser: {
|
||||
id: "xxxx-xxxxx-xxxxx-xxxx",
|
||||
id: 'xxxx-xxxxx-xxxxx-xxxx',
|
||||
displayName: 'Hanako Sato',
|
||||
},
|
||||
getUsers: AdB2cMockUsers,
|
||||
|
||||
@ -80,7 +80,7 @@ export class RedisService {
|
||||
*/
|
||||
async mget<T>(keys: string[]): Promise<{ key: string; value: T | null }[]> {
|
||||
if (keys.length === 0) return []; // mget操作は0件の時エラーとなるため、0件は特別扱いする
|
||||
|
||||
|
||||
try {
|
||||
const records = await this.cacheManager.store.mget(...keys);
|
||||
// getで取得した順序とKeysの順序は一致するはずなので、indexを利用してペアになるよう加工する
|
||||
|
||||
@ -10,6 +10,8 @@ import {
|
||||
JoinColumn,
|
||||
OneToMany,
|
||||
ManyToOne,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { bigintTransformer } from '../../../common/entity';
|
||||
|
||||
@ -37,8 +39,24 @@ export class Task {
|
||||
started_at: Date | null;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
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;
|
||||
|
||||
@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)
|
||||
@JoinColumn({ name: 'audio_file_id' })
|
||||
file: AudioFile | null;
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import {
|
||||
DataSource,
|
||||
EntityManager,
|
||||
FindOptionsOrder,
|
||||
FindOptionsOrderValue,
|
||||
In,
|
||||
IsNull,
|
||||
Repository,
|
||||
} from 'typeorm';
|
||||
import { Task } from './entity/task.entity';
|
||||
import { ADMIN_ROLES, TASK_STATUS, USER_ROLES } from '../../constants';
|
||||
@ -35,6 +37,8 @@ import {
|
||||
import { Roles } from '../../common/types/role';
|
||||
import { TaskStatus, isTaskStatus } from '../../common/types/taskStatus';
|
||||
import { SortCriteria } from '../sort_criteria/entity/sort_criteria.entity';
|
||||
import { Workflow } from '../workflows/entity/workflow.entity';
|
||||
import { Worktype } from '../worktypes/entity/worktype.entity';
|
||||
|
||||
@Injectable()
|
||||
export class TasksRepositoryService {
|
||||
@ -710,18 +714,6 @@ export class TasksRepositoryService {
|
||||
|
||||
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);
|
||||
|
||||
// アカウント内でJOBナンバーが有効なタスクのうち最新のものを取得
|
||||
@ -743,8 +735,19 @@ export class TasksRepositoryService {
|
||||
}
|
||||
task.job_number = newJobNumber;
|
||||
|
||||
const newTask = taskRepo.create(task);
|
||||
const persisted = await taskRepo.save(newTask);
|
||||
const persisted = await taskRepo.save(task);
|
||||
|
||||
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;
|
||||
},
|
||||
);
|
||||
@ -952,6 +955,231 @@ export class TasksRepositoryService {
|
||||
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