Merge branch 'develop' into ccb

# Conflicts:
#	dictation_client/src/translation/de.json
#	dictation_client/src/translation/en.json
#	dictation_client/src/translation/es.json
#	dictation_client/src/translation/fr.json
#	dictation_function/src/test/common/utility.ts
#	dictation_server/src/features/files/test/utility.ts
This commit is contained in:
SAITO-PC-3\saito.k 2024-04-18 09:57:37 +09:00
commit b24059b538
12 changed files with 741 additions and 35 deletions

View File

@ -1,5 +1,5 @@
VITE_STAGE=production VITE_STAGE=production
VITE_B2C_CLIENTID=b0ec473b-6b2b-4f12-adc6-39a24ebe6a3f VITE_B2C_CLIENTID=ea6e2535-c914-4889-8659-7ca1ec2e420d
VITE_B2C_AUTHORITY=https://adb2codmsprod.b2clogin.com/adb2codmsprod.onmicrosoft.com/b2c_1_signin_prod VITE_B2C_AUTHORITY=https://adb2codmsproduction.b2clogin.com/adb2codmsproduction.onmicrosoft.com/b2c_1_signin_production
VITE_B2C_KNOWNAUTHORITIES=adb2codmsprod.b2clogin.com VITE_B2C_KNOWNAUTHORITIES=adb2codmsproduction.b2clogin.com
VITE_DESK_TOP_APP_SCHEME=odms-desktopapp VITE_DESK_TOP_APP_SCHEME=odms-desktopapp

View File

@ -57,7 +57,7 @@ export class Account {
@Column({ nullable: true, type: "datetime" }) @Column({ nullable: true, type: "datetime" })
deleted_at: Date | null; deleted_at: Date | null;
@Column({ nullable: true, type: "datetime" }) @Column({ nullable: true, type: "varchar" })
created_by: string | null; created_by: string | null;
@CreateDateColumn({ @CreateDateColumn({
@ -66,7 +66,7 @@ export class Account {
}) // defaultはSQLite用設定値.本番用は別途migrationで設定 }) // defaultはSQLite用設定値.本番用は別途migrationで設定
created_at: Date; created_at: Date;
@Column({ nullable: true, type: "datetime" }) @Column({ nullable: true, type: "varchar"})
updated_by: string | null; updated_by: string | null;
@UpdateDateColumn({ @UpdateDateColumn({

View File

@ -40,7 +40,7 @@ export class License {
@Column({ nullable: true, type: "bigint", transformer: bigintTransformer }) @Column({ nullable: true, type: "bigint", transformer: bigintTransformer })
delete_order_id: number | null; delete_order_id: number | null;
@Column({ nullable: true, type: "datetime" }) @Column({ nullable: true, type: "varchar" })
created_by: string | null; created_by: string | null;
@CreateDateColumn({ @CreateDateColumn({
@ -49,7 +49,7 @@ export class License {
}) })
created_at: Date; created_at: Date;
@Column({ nullable: true, type: "datetime" }) @Column({ nullable: true, type: "varchar" })
updated_by: string | null; updated_by: string | null;
@UpdateDateColumn({ @UpdateDateColumn({
@ -93,7 +93,7 @@ export class LicenseAllocationHistory {
@Column({ nullable: true, type: "datetime" }) @Column({ nullable: true, type: "datetime" })
deleted_at: Date | null; deleted_at: Date | null;
@Column({ nullable: true, type: "datetime" }) @Column({ nullable: true, type: "varchar" })
created_by: string | null; created_by: string | null;
@CreateDateColumn({ @CreateDateColumn({
@ -102,7 +102,7 @@ export class LicenseAllocationHistory {
}) })
created_at: Date; created_at: Date;
@Column({ nullable: true, type: "datetime" }) @Column({ nullable: true, type: "varchar" })
updated_by: string | null; updated_by: string | null;
@UpdateDateColumn({ @UpdateDateColumn({

View File

@ -32,6 +32,9 @@ export class User {
@Column({ nullable: true, type: "varchar" }) @Column({ nullable: true, type: "varchar" })
accepted_eula_version: string | null; accepted_eula_version: string | null;
@Column({ nullable: true, type: "varchar" })
accepted_privacy_notice_version: string | null;
@Column({ nullable: true, type: "varchar" }) @Column({ nullable: true, type: "varchar" })
accepted_dpa_version: string | null; accepted_dpa_version: string | null;
@ -53,7 +56,7 @@ export class User {
@Column({ nullable: true, type: "datetime" }) @Column({ nullable: true, type: "datetime" })
deleted_at: Date | null; deleted_at: Date | null;
@Column({ nullable: true, type: "datetime" }) @Column({ nullable: true, type: "varchar" })
created_by: string | null; created_by: string | null;
@CreateDateColumn({ @CreateDateColumn({
@ -62,7 +65,7 @@ export class User {
}) // defaultはSQLite用設定値.本番用は別途migrationで設定 }) // defaultはSQLite用設定値.本番用は別途migrationで設定
created_at: Date; created_at: Date;
@Column({ nullable: true, type: "datetime" }) @Column({ nullable: true, type: "varchar" })
updated_by: string | null; updated_by: string | null;
@UpdateDateColumn({ @UpdateDateColumn({

View File

@ -150,9 +150,17 @@ async function getAlertMailTargetAccount(
// 第五のアカウントを取得 // 第五のアカウントを取得
const accountRepository = datasource.getRepository(Account); const accountRepository = datasource.getRepository(Account);
// 管理者ユーザーが規約に同意しているアカウントのみを取得
// 第五階層が同意する規約はeulaとprivacy_noticeのみであるため、それらがnullでないことを条件に追加
// 管理者が規約同意していない = 一度もログインしていないアカウントであるため、そのアカウントにはアラートメールを送信しない
// プロダクト バックログ項目 4073: [保守]システムに一度もログインしていないユーザーにはライセンスアラートメールを送信しないようにしたい の対応
const accounts = await accountRepository.find({ const accounts = await accountRepository.find({
where: { where: {
tier: TIERS.TIER5, tier: TIERS.TIER5,
primaryAdminUser: {
accepted_eula_version: Not(IsNull()),
accepted_privacy_notice_version: Not(IsNull()),
},
}, },
relations: { relations: {
primaryAdminUser: true, primaryAdminUser: true,

View File

@ -127,7 +127,7 @@ export const makeTestAccount = async (
verified: d?.verified ?? true, verified: d?.verified ?? true,
auto_file_delete: d?.auto_file_delete ?? false, auto_file_delete: d?.auto_file_delete ?? false,
file_retention_days: d?.file_retention_days ?? 30, file_retention_days: d?.file_retention_days ?? 30,
deleted_at: d?.deleted_at ?? "", deleted_at: d?.deleted_at ?? null,
created_by: d?.created_by ?? "test_runner", created_by: d?.created_by ?? "test_runner",
created_at: d?.created_at ?? new Date(), created_at: d?.created_at ?? new Date(),
updated_by: d?.updated_by ?? "updater", updated_by: d?.updated_by ?? "updater",
@ -143,8 +143,16 @@ export const makeTestAccount = async (
account_id: accountId, account_id: accountId,
role: d?.role ?? "none", role: d?.role ?? "none",
author_id: d?.author_id ?? undefined, author_id: d?.author_id ?? undefined,
accepted_eula_version: d?.accepted_eula_version ?? "1.0", accepted_eula_version:
accepted_dpa_version: d?.accepted_dpa_version ?? "1.0", d?.accepted_eula_version !== undefined
? d.accepted_eula_version
: "1.0",
accepted_privacy_notice_version:
d?.accepted_privacy_notice_version !== undefined
? d.accepted_privacy_notice_version
: "1.0",
accepted_dpa_version:
d?.accepted_dpa_version !== undefined ? d.accepted_dpa_version : "1.0",
email_verified: d?.email_verified ?? true, email_verified: d?.email_verified ?? true,
auto_renew: d?.auto_renew ?? true, auto_renew: d?.auto_renew ?? true,
notification: d?.notification ?? true, notification: d?.notification ?? true,

View File

@ -54,7 +54,7 @@ describe("licenseAlert", () => {
const { account, admin } = await makeTestAccount( const { account, admin } = await makeTestAccount(
source, source,
{ tier: 5 }, { tier: 5 },
{ external_id: "external_id1" } { external_id: "external_id1", accepted_dpa_version: null }
); );
await createLicense( await createLicense(
source, source,
@ -97,7 +97,11 @@ describe("licenseAlert", () => {
const { account, admin } = await makeTestAccount( const { account, admin } = await makeTestAccount(
source, source,
{ tier: 5 }, { tier: 5 },
{ external_id: "external_id2", auto_renew: false } {
external_id: "external_id2",
auto_renew: false,
accepted_dpa_version: null,
}
); );
await createLicense( await createLicense(
source, source,
@ -141,7 +145,7 @@ describe("licenseAlert", () => {
const { account, admin } = await makeTestAccount( const { account, admin } = await makeTestAccount(
source, source,
{ tier: 5 }, { tier: 5 },
{ external_id: "external_id3" } { external_id: "external_id3", accepted_dpa_version: null }
); );
await createLicense( await createLicense(
source, source,
@ -196,7 +200,11 @@ describe("licenseAlert", () => {
const { account, admin } = await makeTestAccount( const { account, admin } = await makeTestAccount(
source, source,
{ tier: 5 }, { tier: 5 },
{ external_id: "external_id4", auto_renew: true } {
external_id: "external_id4",
auto_renew: true,
accepted_dpa_version: null,
}
); );
await createLicense( await createLicense(
source, source,
@ -224,6 +232,50 @@ describe("licenseAlert", () => {
const keys = await getAsync(`*`); const keys = await getAsync(`*`);
expect(keys).toHaveLength(0); expect(keys).toHaveLength(0);
}); });
it("規約同意していない管理者ユーザーに対して、ライセンス失効警告メール・在庫不足メールが送信されないこと", async () => {
if (!source) fail();
const context = new InvocationContext();
const sendgridMock = new SendGridServiceMock() as SendGridService;
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
// 呼び出し回数でテスト成否を判定
const spySend = jest.spyOn(sendgridMock, "sendMail");
const currentDate = new DateWithZeroTime();
const expiringSoonDate = new DateWithDayEndTime(currentDate.getTime());
const { account, admin } = await makeTestAccount(
source,
{ tier: 5 },
{
external_id: "external_id5",
auto_renew: false,
accepted_eula_version: null,
accepted_privacy_notice_version: null,
}
);
await createLicense(
source,
1,
expiringSoonDate,
account.id,
"NORMAL",
"Allocated",
admin.id,
null,
null,
null
);
await licenseAlertProcessing(
context,
source,
redisClient,
sendgridMock,
adb2cMock
);
// メール送信がされていないことを確認
expect(spySend.mock.calls).toHaveLength(0);
});
}); });
// テスト用sendgrid // テスト用sendgrid
@ -305,6 +357,17 @@ export class AdB2cServiceMock {
}, },
], ],
}, },
{
id: "external_id5",
displayName: "test5",
identities: [
{
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: "issuer",
issuerAssignedId: "test5@mail.com",
},
],
},
]; ];
return AdB2cMockUsers; return AdB2cMockUsers;
} }

View File

@ -8,6 +8,7 @@ import {
createUserGroupAndMember, createUserGroupAndMember,
getTaskFromJobNumber, getTaskFromJobNumber,
makeTestingModuleWithBlobAndNotification, makeTestingModuleWithBlobAndNotification,
updateTaskStatusAndIsJobNumberEnabled,
} from './test/utility'; } from './test/utility';
import { FilesService } from './files.service'; import { FilesService } from './files.service';
import { makeContext } from '../../common/log'; import { makeContext } from '../../common/log';
@ -331,7 +332,7 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
); );
if (!module) fail(); if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
const NotificationHubService = module.get<NotificationhubService>( const notificationHubService = module.get<NotificationhubService>(
NotificationhubService, NotificationhubService,
); );
const result = await service.uploadFinished( const result = await service.uploadFinished(
@ -352,9 +353,9 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
optionItemList, optionItemList,
false, false,
); );
expect(result).toEqual({ jobNumber: '00000001' }); expect(result.jobNumber).toEqual('00000001');
// 通知処理が想定通りの引数で呼ばれているか確認 // 通知処理が想定通りの引数で呼ばれているか確認
expect(NotificationHubService.notify).toHaveBeenCalledWith( expect(notificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId', 'requestId'), makeContext('trackingId', 'requestId'),
[`user_${typistUserId}`], [`user_${typistUserId}`],
{ {
@ -432,7 +433,7 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
); );
if (!module) fail(); if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
const NotificationHubService = module.get<NotificationhubService>( const notificationHubService = module.get<NotificationhubService>(
NotificationhubService, NotificationhubService,
); );
const result = await service.uploadFinished( const result = await service.uploadFinished(
@ -453,9 +454,9 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
optionItemList, optionItemList,
false, false,
); );
expect(result).toEqual({ jobNumber: '00000002' }); expect(result.jobNumber).toEqual('00000002');
// 通知処理が想定通りの引数で呼ばれているか確認 // 通知処理が想定通りの引数で呼ばれているか確認
expect(NotificationHubService.notify).toHaveBeenCalledWith( expect(notificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId', 'requestId'), makeContext('trackingId', 'requestId'),
[`user_${typistUserId}`], [`user_${typistUserId}`],
{ {
@ -555,7 +556,7 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
); );
if (!module) fail(); if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
const NotificationHubService = module.get<NotificationhubService>( const notificationHubService = module.get<NotificationhubService>(
NotificationhubService, NotificationhubService,
); );
const result = await service.uploadFinished( const result = await service.uploadFinished(
@ -576,9 +577,9 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
optionItemList, optionItemList,
false, false,
); );
expect(result).toEqual({ jobNumber: '00000001' }); expect(result.jobNumber).toEqual('00000001');
// 通知処理が想定通りの引数で呼ばれているか確認 // 通知処理が想定通りの引数で呼ばれているか確認
expect(NotificationHubService.notify).toHaveBeenCalledWith( expect(notificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId', 'requestId'), makeContext('trackingId', 'requestId'),
[`user_${typistUserId}`], [`user_${typistUserId}`],
{ {
@ -677,7 +678,7 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
); );
if (!module) fail(); if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
const NotificationHubService = module.get<NotificationhubService>( const notificationHubService = module.get<NotificationhubService>(
NotificationhubService, NotificationhubService,
); );
const result = await service.uploadFinished( const result = await service.uploadFinished(
@ -698,9 +699,9 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
optionItemList, optionItemList,
false, false,
); );
expect(result).toEqual({ jobNumber: '00000001' }); expect(result.jobNumber).toEqual('00000001');
// 通知処理が想定通りの引数で呼ばれているか確認 // 通知処理が想定通りの引数で呼ばれているか確認
expect(NotificationHubService.notify).toHaveBeenCalledWith( expect(notificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId', 'requestId'), makeContext('trackingId', 'requestId'),
[`user_${typistUserId}`], [`user_${typistUserId}`],
{ {
@ -764,7 +765,7 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
optionItemList, optionItemList,
false, false,
); );
expect(result).toEqual({ jobNumber: '00000001' }); expect(result.jobNumber).toEqual('00000001');
// タスクを取得 // タスクを取得
const resultTask = await getTaskFromJobNumber(source, result.jobNumber); const resultTask = await getTaskFromJobNumber(source, result.jobNumber);
// タスクのチェックアウト権限を取得 // タスクのチェックアウト権限を取得
@ -1307,6 +1308,613 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
), ),
); );
}); });
it('タスク作成時に採番されるジョブナンバーが常に最新タスクのジョブナンバー + 1となる(最新タスクのジョブナンバーが有効でない場合)', 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();
// タスクを10件作成, ジョブナンバーは00000001から00000010まで
for (let i = 1; i <= 10; i++) {
await createTask(
source,
accountId,
`http://blob/url/file_${i}.zip`,
`file_${i}.zip`,
'01',
typistUserId,
authorAuthorId ?? '',
undefined,
undefined,
i.toString().padStart(8, '0'),
);
}
// 最新のジョブナンバーでタスクを取得
const latestTask = await getTaskFromJobNumber(source, '00000010');
// 最新のタスクのステータスとis_job_number_enabledを更新
await updateTaskStatusAndIsJobNumberEnabled(
source,
latestTask?.id ?? 0,
'Backup',
false,
);
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', 'requestId'),
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.jobNumber).toEqual('00000011');
// 通知処理が想定通りの引数で呼ばれているか確認
expect(notificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId', 'requestId'),
[`user_${typistUserId}`],
{
authorId: 'AUTHOR_ID',
filename: 'file',
priority: 'High',
uploadedAt: '2023-05-26T11:22:33.444',
},
);
// 作成したタスクを取得
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('タスク作成時に採番されるジョブナンバーが常に最新タスクのジョブナンバー + 1となる(最新タスクのジョブナンバー有効である場合)', 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();
// タスクを10件作成, ジョブナンバーは00000001から00000010まで
for (let i = 1; i <= 10; i++) {
await createTask(
source,
accountId,
`http://blob/url/file_${i}.zip`,
`file_${i}.zip`,
'01',
typistUserId,
authorAuthorId ?? '',
undefined,
undefined,
i.toString().padStart(8, '0'),
);
}
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', 'requestId'),
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.jobNumber).toEqual('00000011');
// 通知処理が想定通りの引数で呼ばれているか確認
expect(notificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId', 'requestId'),
[`user_${typistUserId}`],
{
authorId: 'AUTHOR_ID',
filename: 'file',
priority: 'High',
uploadedAt: '2023-05-26T11:22:33.444',
},
);
// 作成したタスクを取得
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('タスク作成時に採番されるジョブナンバーが常に最新タスクのジョブナンバー + 1となる(途中のタスクのジョブナンバーが有効でない場合)', 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();
// タスクを10件作成, ジョブナンバーは00000001から00000010まで
for (let i = 1; i <= 10; i++) {
await createTask(
source,
accountId,
`http://blob/url/file_${i}.zip`,
`file_${i}.zip`,
'01',
typistUserId,
authorAuthorId ?? '',
undefined,
undefined,
i.toString().padStart(8, '0'),
);
}
// ジョブナンバー00000005のタスクを取得
const middleTask = await getTaskFromJobNumber(source, '00000005');
// ジョブナンバー00000005のタスクのステータスとis_job_number_enabledを更新
await updateTaskStatusAndIsJobNumberEnabled(
source,
middleTask?.id ?? 0,
'Backup',
false,
);
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', 'requestId'),
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.jobNumber).toEqual('00000011');
// 通知処理が想定通りの引数で呼ばれているか確認
expect(notificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId', 'requestId'),
[`user_${typistUserId}`],
{
authorId: 'AUTHOR_ID',
filename: 'file',
priority: 'High',
uploadedAt: '2023-05-26T11:22:33.444',
},
);
// 作成したタスクを取得
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('タスク作成時に採番されるジョブナンバーが常に最新タスクのジョブナンバー + 1となる(最新タスクのジョブナンバーが99999999で有効でない場合)', 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();
// タスクを10件作成, ジョブナンバーは99999989から99999999まで
for (let i = 99999989; i <= 99999999; i++) {
await createTask(
source,
accountId,
`http://blob/url/file_${i}.zip`,
`file_${i}.zip`,
'01',
typistUserId,
authorAuthorId ?? '',
undefined,
undefined,
i.toString().padStart(8, '0'),
);
}
// 最新ジョブナンバーのタスクを取得
const latestTask = await getTaskFromJobNumber(source, '99999999');
// 最新のタスクのステータスとis_job_number_enabledを更新
await updateTaskStatusAndIsJobNumberEnabled(
source,
latestTask?.id ?? 0,
'Backup',
false,
);
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', 'requestId'),
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.jobNumber).toEqual('00000001');
// 通知処理が想定通りの引数で呼ばれているか確認
expect(notificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId', 'requestId'),
[`user_${typistUserId}`],
{
authorId: 'AUTHOR_ID',
filename: 'file',
priority: 'High',
uploadedAt: '2023-05-26T11:22:33.444',
},
);
// 作成したタスクを取得
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('タスク作成時に採番されるジョブナンバーが常に最新タスクのジョブナンバー + 1となる(最新タスクのジョブナンバーが99999999で有効な場合)', 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();
// タスクを10件作成, ジョブナンバーは99999989から99999999まで
for (let i = 99999989; i <= 99999999; i++) {
await createTask(
source,
accountId,
`http://blob/url/file_${i}.zip`,
`file_${i}.zip`,
'01',
typistUserId,
authorAuthorId ?? '',
undefined,
undefined,
i.toString().padStart(8, '0'),
);
}
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', 'requestId'),
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.jobNumber).toEqual('00000001');
// 通知処理が想定通りの引数で呼ばれているか確認
expect(notificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId', 'requestId'),
[`user_${typistUserId}`],
{
authorId: 'AUTHOR_ID',
filename: 'file',
priority: 'High',
uploadedAt: '2023-05-26T11:22:33.444',
},
);
// 作成したタスクを取得
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);
});
}); });
describe('音声ファイルダウンロードURL取得', () => { describe('音声ファイルダウンロードURL取得', () => {

View File

@ -121,6 +121,21 @@ export const getTaskFromJobNumber = async (
return task; return task;
}; };
// 指定したタスクのステータスとis_job_number_enabledを更新する
export const updateTaskStatusAndIsJobNumberEnabled = async (
datasource: DataSource,
taskId: number,
status: string,
isJobNumberEnabled: boolean,
): Promise<void> => {
await datasource
.getRepository(Task)
.update(
{ id: taskId },
{ status: status, is_job_number_enabled: isJobNumberEnabled },
);
};
// ユーザーグループとユーザーグループメンバーを作成する // ユーザーグループとユーザーグループメンバーを作成する
export const createUserGroupAndMember = async ( export const createUserGroupAndMember = async (
datasource: DataSource, datasource: DataSource,

View File

@ -117,7 +117,7 @@ export class LicensesService {
parentAccountId, parentAccountId,
); );
this.sendgridService.sendMailWithU105( await this.sendgridService.sendMailWithU105(
context, context,
customer.adminEmails, customer.adminEmails,
customer.companyName, customer.companyName,

View File

@ -492,7 +492,7 @@ export class TasksService {
getUserNameAndMailAddress(primaryAdmin); getUserNameAndMailAddress(primaryAdmin);
// メール送信 // メール送信
this.sendgridService.sendMailWithU117( await this.sendgridService.sendMailWithU117(
context, context,
authorNotification ? authorEmail : null, authorNotification ? authorEmail : null,
typistEmail, typistEmail,

View File

@ -905,9 +905,10 @@ export class TasksRepositoryService {
const taskRepo = entityManager.getRepository(Task); const taskRepo = entityManager.getRepository(Task);
// アカウント内でJOBナンバーが有効なタスクのうち最新のものを取得 // バグ 3954: [4/8リリース]タスクをすべてBackupした後、タスクを作成するとジョブナンバーがから採番される暫定対応
// アカウント内で最新タスクのタスクを取得し、そのJOBナンバーをインクリメントして新しいタスクのJOBナンバーを設定する
const lastTask = await taskRepo.findOne({ const lastTask = await taskRepo.findOne({
where: { account_id: account_id, is_job_number_enabled: true }, where: { account_id: account_id },
order: { created_at: 'DESC', job_number: 'DESC' }, order: { created_at: 'DESC', job_number: 'DESC' },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' }, lock: { mode: 'pessimistic_write' },