diff --git a/data_migration_tools/server/src/common/types/types.ts b/data_migration_tools/server/src/common/types/types.ts index 660b942..de92211 100644 --- a/data_migration_tools/server/src/common/types/types.ts +++ b/data_migration_tools/server/src/common/types/types.ts @@ -134,7 +134,7 @@ export function isAccountsInputFile(obj: any): obj is AccountsInputFile { "country" in obj && typeof obj.country === "string" && ("dealerAccountId" in obj - ? typeof obj.dealerAccountId === "number" + ? obj.dealerAccountId === null || typeof obj.dealerAccountId === "number" : true) && "adminName" in obj && typeof obj.adminName === "string" && diff --git a/data_migration_tools/server/src/repositories/accounts/accounts.repository.service.ts b/data_migration_tools/server/src/repositories/accounts/accounts.repository.service.ts index ec24808..b38f6ba 100644 --- a/data_migration_tools/server/src/repositories/accounts/accounts.repository.service.ts +++ b/data_migration_tools/server/src/repositories/accounts/accounts.repository.service.ts @@ -1,20 +1,18 @@ -import { Injectable } from '@nestjs/common'; -import { - DataSource, -} from 'typeorm'; -import { User } from '../users/entity/user.entity'; -import { Account } from './entity/account.entity'; +import { Injectable } from "@nestjs/common"; +import { DataSource } from "typeorm"; +import { User } from "../users/entity/user.entity"; +import { Account } from "./entity/account.entity"; import { getDirection, getTaskListSortableAttribute, -} from '../../common/types/sort/util'; +} from "../../common/types/sort/util"; import { SortCriteria } from "../sort_criteria/entity/sort_criteria.entity"; import { insertEntity, updateEntity, deleteEntity, -} from '../../common/repository'; -import { Context } from '../../common/log'; +} from "../../common/repository"; +import { Context } from "../../common/log"; @Injectable() export class AccountsRepositoryService { @@ -81,6 +79,7 @@ export class AccountsRepositoryService { user.accepted_privacy_notice_version = adminUserAcceptedPrivacyNoticeVersion ?? null; user.accepted_dpa_version = adminUserAcceptedDpaVersion ?? null; + user.email_verified = true; } const usersRepo = entityManager.getRepository(User); const newUser = usersRepo.create(user); diff --git a/data_migration_tools/server/src/repositories/licenses/entity/license.entity.ts b/data_migration_tools/server/src/repositories/licenses/entity/license.entity.ts index 90715ae..57ba60c 100644 --- a/data_migration_tools/server/src/repositories/licenses/entity/license.entity.ts +++ b/data_migration_tools/server/src/repositories/licenses/entity/license.entity.ts @@ -129,6 +129,33 @@ export class CardLicense { @Column({ nullable: true, type: "datetime" }) updated_by: string | null; + @UpdateDateColumn({ + default: () => "datetime('now', 'localtime')", + type: "datetime", + }) + updated_at: Date; +} + +@Entity({ name: "card_license_issue" }) +export class CardLicenseIssue { + @PrimaryGeneratedColumn() + id: number; + + @Column() + issued_at: Date; + + @Column({ nullable: true, type: "datetime" }) + created_by: string | null; + + @CreateDateColumn({ + default: () => "datetime('now', 'localtime')", + type: "datetime", + }) + created_at: Date; + + @Column({ nullable: true, type: "datetime" }) + updated_by: string | null; + @UpdateDateColumn({ default: () => "datetime('now', 'localtime')", type: "datetime", diff --git a/data_migration_tools/server/src/repositories/licenses/licenses.repository.module.ts b/data_migration_tools/server/src/repositories/licenses/licenses.repository.module.ts index e3e3d0c..006f054 100644 --- a/data_migration_tools/server/src/repositories/licenses/licenses.repository.module.ts +++ b/data_migration_tools/server/src/repositories/licenses/licenses.repository.module.ts @@ -2,6 +2,7 @@ import { Module } from "@nestjs/common"; import { TypeOrmModule } from "@nestjs/typeorm"; import { CardLicense, + CardLicenseIssue, License, LicenseAllocationHistory, } from "./entity/license.entity"; @@ -9,7 +10,11 @@ import { LicensesRepositoryService } from "./licenses.repository.service"; @Module({ imports: [ - TypeOrmModule.forFeature([License, CardLicense, LicenseAllocationHistory]), + TypeOrmModule.forFeature([ + License, + CardLicense, + CardLicenseIssue, LicenseAllocationHistory, + ]), ], providers: [LicensesRepositoryService], exports: [LicensesRepositoryService], diff --git a/data_migration_tools/server/src/repositories/licenses/licenses.repository.service.ts b/data_migration_tools/server/src/repositories/licenses/licenses.repository.service.ts index 52e13b5..643b7f4 100644 --- a/data_migration_tools/server/src/repositories/licenses/licenses.repository.service.ts +++ b/data_migration_tools/server/src/repositories/licenses/licenses.repository.service.ts @@ -4,14 +4,19 @@ import { License, LicenseAllocationHistory, CardLicense, + CardLicenseIssue, } from "./entity/license.entity"; -import { insertEntities } from "../../common/repository"; +import { insertEntity, insertEntities } from "../../common/repository"; import { Context } from "../../common/log"; import { LicensesInputFile, CardLicensesInputFile, } from "../../common/types/types"; - +import {AUTO_INCREMENT_START} from "../../constants/index" +import { + LICENSE_ALLOCATED_STATUS, + LICENSE_TYPE, +} from "../../constants"; @Injectable() export class LicensesRepositoryService { //クエリログにコメントを出力するかどうか @@ -38,7 +43,9 @@ export class LicensesRepositoryService { license.account_id = licensesInputFile.account_id; license.status = licensesInputFile.status; license.type = licensesInputFile.type; - license.expiry_date = (licensesInputFile.expiry_date) ? new Date(licensesInputFile.expiry_date) : null; + license.expiry_date = licensesInputFile.expiry_date + ? new Date(licensesInputFile.expiry_date) + : null; if (licensesInputFile.allocated_user_id) { license.allocated_user_id = licensesInputFile.allocated_user_id; } @@ -97,22 +104,61 @@ export class LicensesRepositoryService { ): Promise<{}> { return await this.dataSource.transaction(async (entityManager) => { const cardLicenseRepo = entityManager.getRepository(CardLicense); + const licensesRepo = entityManager.getRepository(License); + const cardLicenseIssueRepo = + entityManager.getRepository(CardLicenseIssue); + const licenses: License[] = []; + // ライセンステーブルを作成する(BULK INSERT) + for (let i = 0; i < cardLicensesInputFiles.length; i++) { + const license = new License(); + license.account_id = AUTO_INCREMENT_START; // 最初に登場するアカウント(第一アカウント) + license.status = LICENSE_ALLOCATED_STATUS.UNALLOCATED; + license.type = LICENSE_TYPE.CARD; + licenses.push(license); + } + const savedLicenses = await insertEntities( + License, + licensesRepo, + licenses, + this.isCommentOut, + context + ); - let newCardLicenses: CardLicense[] = []; - cardLicensesInputFiles.forEach((cardLicensesInputFile) => { + // カードライセンス発行テーブルを作成する + const cardLicenseIssue = new CardLicenseIssue(); + cardLicenseIssue.issued_at = new Date(); + const newCardLicenseIssue = cardLicenseIssueRepo.create(cardLicenseIssue); + const savedCardLicensesIssue = await insertEntity( + CardLicenseIssue, + cardLicenseIssueRepo, + newCardLicenseIssue, + this.isCommentOut, + context + ); + + const newCardLicenses: CardLicense[] = []; + // カードライセンステーブルを作成する(BULK INSERT) + for (let i = 0; i < cardLicensesInputFiles.length; i++) { const cardLicense = new CardLicense(); - cardLicense.license_id = cardLicensesInputFile.license_id; - cardLicense.issue_id = cardLicensesInputFile.issue_id; - cardLicense.card_license_key = cardLicensesInputFile.card_license_key; - cardLicense.activated_at = (cardLicensesInputFile.activated_at) ? new Date(cardLicensesInputFile.activated_at) : null; - cardLicense.created_at = (cardLicensesInputFile.created_at) ? new Date(cardLicensesInputFile.created_at) : null; - cardLicense.created_by = cardLicensesInputFile.created_by; - cardLicense.updated_at = (cardLicensesInputFile.updated_at) ? new Date(cardLicensesInputFile.updated_at) : null; - cardLicense.updated_by = cardLicensesInputFile.updated_by; + cardLicense.license_id = savedLicenses[i].id; // Licenseテーブルの自動採番されたIDを挿入 + cardLicense.issue_id = savedCardLicensesIssue.id; // CardLicenseIssueテーブルの自動採番されたIDを挿入 + cardLicense.card_license_key = + cardLicensesInputFiles[i].card_license_key; + cardLicense.activated_at = cardLicensesInputFiles[i].activated_at + ? new Date(cardLicensesInputFiles[i].activated_at) + : null; + cardLicense.created_at = cardLicensesInputFiles[i].created_at + ? new Date(cardLicensesInputFiles[i].created_at) + : null; + cardLicense.created_by = cardLicensesInputFiles[i].created_by; + cardLicense.updated_at = cardLicensesInputFiles[i].updated_at + ? new Date(cardLicensesInputFiles[i].updated_at) + : null; + cardLicense.updated_by = cardLicensesInputFiles[i].updated_by; newCardLicenses.push(cardLicense); - }); + } const query = cardLicenseRepo .createQueryBuilder() @@ -126,5 +172,4 @@ export class LicensesRepositoryService { return {}; }); } - } diff --git a/dictation_client/src/pages/PartnerPage/addPartnerAccountPopup.tsx b/dictation_client/src/pages/PartnerPage/addPartnerAccountPopup.tsx index edf782c..48b36a2 100644 --- a/dictation_client/src/pages/PartnerPage/addPartnerAccountPopup.tsx +++ b/dictation_client/src/pages/PartnerPage/addPartnerAccountPopup.tsx @@ -11,6 +11,7 @@ import { selectEmail, selectInputValidationErrors, selectIsLoading, + selectOffset, } from "features/partner/selectors"; import { changeAdminName, @@ -19,7 +20,11 @@ import { changeEmail, cleanupAddPartner, } from "features/partner/partnerSlice"; -import { createPartnerAccountAsync } from "features/partner"; +import { + LIMIT_PARTNER_VIEW_NUM, + createPartnerAccountAsync, + getPartnerInfoAsync, +} from "features/partner"; import close from "../../assets/images/close.svg"; import progress_activit from "../../assets/images/progress_activit.svg"; import { COUNTRY_LIST } from "../SignupPage/constants"; @@ -50,6 +55,7 @@ export const AddPartnerAccountPopup: React.FC = ( const adminName = useSelector(selectAdminName); const email = useSelector(selectEmail); const isLoading = useSelector(selectIsLoading); + const offset = useSelector(selectOffset); // ポップアップを閉じる処理 const closePopup = useCallback(() => { @@ -84,6 +90,12 @@ export const AddPartnerAccountPopup: React.FC = ( setIsPushCreateButton(false); if (meta.requestStatus === "fulfilled") { + dispatch( + getPartnerInfoAsync({ + limit: LIMIT_PARTNER_VIEW_NUM, + offset, + }) + ); closePopup(); } }, [ diff --git a/dictation_server/src/features/tasks/tasks.service.spec.ts b/dictation_server/src/features/tasks/tasks.service.spec.ts index 27ffcfe..1951519 100644 --- a/dictation_server/src/features/tasks/tasks.service.spec.ts +++ b/dictation_server/src/features/tasks/tasks.service.spec.ts @@ -786,6 +786,103 @@ describe('TasksService', () => { expect(task.optionItemList).toEqual(audioOptionItems); } }); + + it('[Author] Authorは自分が作成者のTask一覧を取得できる(ソート条件がJob_number以外)', async () => { + const notificationhubServiceMockValue = + makeDefaultNotificationhubServiceMockValue(); + if (!source) fail(); + const module = await makeTaskTestingModuleWithNotificaiton( + source, + notificationhubServiceMockValue, + ); + if (!module) fail(); + const { id: accountId } = await makeTestSimpleAccount(source); + const { id: userId, external_id } = await makeTestUser(source, { + account_id: accountId, + external_id: 'userId', + role: 'author', + author_id: 'MY_AUTHOR_ID', + }); + + //「バグ 3661: [FB対応]Option Itemにチェックを付けると真っ白な画面になる」の確認のため + // audio_file_idをTaskIdと異なる値にするために、AudioFileを作成 + await createAudioFile( + source, + accountId, + userId, + 'MY_AUTHOR_ID', + '', + '00', + ); + + // Taskを作成 + await createTask( + source, + accountId, + userId, + 'MY_AUTHOR_ID', + 'WORKTYPE1', + '01', + '00000001', + 'Uploaded', + ); + await createTask( + source, + accountId, + userId, + 'MY_AUTHOR_ID', + 'WORKTYPE2', + '01', + '00000002', + 'Uploaded', + ); + + const service = module.get(TasksService); + const offset = 0; + const limit = 20; + const status = ['Uploaded', 'Backup']; + // バグ 3786: [FB対応]タスク一覧画面のOptionItemがソート条件によって表示順がおかしくなる の確認のため + // Job_number以外のソート条件を指定 + const paramName = 'WORK_TYPE'; + const direction = 'DESC'; + + const { tasks, total } = await service.getTasks( + makeContext('trackingId', 'requestId'), + external_id, + [USER_ROLES.AUTHOR], + offset, + limit, + status, + paramName, + direction, + ); + + expect(total).toEqual(2); + { + const task = tasks[0]; + expect(task.jobNumber).toEqual('00000002'); + // ソート条件がJob_number以外でもOptionItemがid順に取得されていることを確認 + const audioOptionItems = Array.from({ length: 10 }).map((_, i) => { + return { + optionItemLabel: `label${i}:audio_file_id${task.audioFileId}`, + optionItemValue: `value${i}:audio_file_id${task.audioFileId}`, + }; + }); + expect(task.optionItemList).toEqual(audioOptionItems); + } + { + const task = tasks[1]; + expect(task.jobNumber).toEqual('00000001'); + // ソート条件がJob_number以外でもOptionItemがid順に取得されていることを確認 + const audioOptionItems = Array.from({ length: 10 }).map((_, i) => { + return { + optionItemLabel: `label${i}:audio_file_id${task.audioFileId}`, + optionItemValue: `value${i}:audio_file_id${task.audioFileId}`, + }; + }); + expect(task.optionItemList).toEqual(audioOptionItems); + } + }); it('[Author] Authorは同一アカウントであっても自分以外のAuhtorのTaskは取得できない', async () => { const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); diff --git a/dictation_server/src/features/users/users.service.spec.ts b/dictation_server/src/features/users/users.service.spec.ts index c738fb6..5ecb653 100644 --- a/dictation_server/src/features/users/users.service.spec.ts +++ b/dictation_server/src/features/users/users.service.spec.ts @@ -179,7 +179,7 @@ describe('UsersService.confirmUser', () => { }); expect(_subject).toBe('Account Registered Notification [U-101]'); expect(_url).toBe('http://localhost:8081/'); - }, 600000); + }); it('トークンの形式が不正な場合、形式不正エラーとなる。', async () => { if (!source) fail(); @@ -2744,17 +2744,21 @@ describe('UsersService.getRelations', () => { const worktype1 = await createWorktype( source, account.id, - 'worktype1', + 'worktypeB', undefined, true, ); await createOptionItems(source, worktype1.id); - const worktype2 = await createWorktype(source, account.id, 'worktype2'); + const worktype2 = await createWorktype(source, account.id, 'worktypeC'); await createOptionItems(source, worktype2.id); + const worktype3 = await createWorktype(source, account.id, 'worktypeA'); + await createOptionItems(source, worktype3.id); + await createWorkflow(source, account.id, user1, worktype1.id); await createWorkflow(source, account.id, user1, worktype2.id); + await createWorkflow(source, account.id, user1, worktype3.id); await createWorkflow(source, account.id, user1); await createWorkflow(source, account.id, user2, worktype1.id); @@ -2762,15 +2766,18 @@ describe('UsersService.getRelations', () => { { const workflows = await getWorkflows(source, account.id); workflows.sort((a, b) => a.id - b.id); - expect(workflows.length).toBe(4); + + expect(workflows.length).toBe(5); expect(workflows[0].worktype_id).toBe(worktype1.id); expect(workflows[0].author_id).toBe(user1); expect(workflows[1].worktype_id).toBe(worktype2.id); expect(workflows[1].author_id).toBe(user1); - expect(workflows[2].worktype_id).toBe(null); + expect(workflows[2].worktype_id).toBe(worktype3.id); expect(workflows[2].author_id).toBe(user1); - expect(workflows[3].worktype_id).toBe(worktype1.id); - expect(workflows[3].author_id).toBe(user2); + expect(workflows[3].worktype_id).toBe(null); + expect(workflows[3].author_id).toBe(user1); + expect(workflows[4].worktype_id).toBe(worktype1.id); + expect(workflows[4].author_id).toBe(user2); } const context = makeContext(external_id, 'requestId'); @@ -2786,14 +2793,17 @@ describe('UsersService.getRelations', () => { expect(relations.authorIdList[1]).toBe('AUTHOR_2'); const workTypeList = relations.workTypeList; - expect(relations.workTypeList.length).toBe(2); - expect(workTypeList[0].workTypeId).toBe(worktype1.custom_worktype_id); + expect(relations.workTypeList.length).toBe(3); + // Workflowの作成順ではなくcustom_worktype_idの昇順で取得するためWorkTypeListの先頭はworktype3 + expect(workTypeList[0].workTypeId).toBe(worktype3.custom_worktype_id); expect(workTypeList[0].optionItemList.length).toBe(10); expect(workTypeList[0].optionItemList[0].label).toBe(''); expect(workTypeList[0].optionItemList[0].initialValueType).toBe(2); expect(workTypeList[0].optionItemList[0].defaultValue).toBe(''); - expect(workTypeList[1].workTypeId).toBe(worktype2.custom_worktype_id); + expect(workTypeList[1].workTypeId).toBe(worktype1.custom_worktype_id); expect(workTypeList[1].optionItemList.length).toBe(10); + expect(workTypeList[2].workTypeId).toBe(worktype2.custom_worktype_id); + expect(workTypeList[2].optionItemList.length).toBe(10); expect(relations.isEncrypted).toBe(true); expect(relations.encryptionPassword).toBe('password'); diff --git a/dictation_server/src/repositories/tasks/tasks.repository.service.ts b/dictation_server/src/repositories/tasks/tasks.repository.service.ts index d712597..4302a9b 100644 --- a/dictation_server/src/repositories/tasks/tasks.repository.service.ts +++ b/dictation_server/src/repositories/tasks/tasks.repository.service.ts @@ -1582,78 +1582,91 @@ const makeOrder = ( priority: 'DESC', job_number: direction, id: 'ASC', + option_items: { id: 'ASC' }, }; case 'STATUS': return { priority: 'DESC', status: direction, id: 'ASC', + option_items: { id: 'ASC' }, }; case 'TRANSCRIPTION_FINISHED_DATE': return { priority: 'DESC', finished_at: direction, id: 'ASC', + option_items: { id: 'ASC' }, }; case 'TRANSCRIPTION_STARTED_DATE': return { priority: 'DESC', started_at: direction, id: 'ASC', + option_items: { id: 'ASC' }, }; case 'AUTHOR_ID': return { priority: 'DESC', file: { author_id: direction }, id: 'ASC', + option_items: { id: 'ASC' }, }; case 'ENCRYPTION': return { priority: 'DESC', file: { is_encrypted: direction }, id: 'ASC', + option_items: { id: 'ASC' }, }; case 'FILE_LENGTH': return { priority: 'DESC', file: { duration: direction }, id: 'ASC', + option_items: { id: 'ASC' }, }; case 'FILE_NAME': return { priority: 'DESC', file: { file_name: direction }, id: 'ASC', + option_items: { id: 'ASC' }, }; case 'FILE_SIZE': return { priority: 'DESC', file: { file_size: direction }, id: 'ASC', + option_items: { id: 'ASC' }, }; case 'RECORDING_FINISHED_DATE': return { priority: 'DESC', file: { finished_at: direction }, id: 'ASC', + option_items: { id: 'ASC' }, }; case 'RECORDING_STARTED_DATE': return { priority: 'DESC', file: { started_at: direction }, id: 'ASC', + option_items: { id: 'ASC' }, }; case 'UPLOAD_DATE': return { priority: 'DESC', file: { uploaded_at: direction }, id: 'ASC', + option_items: { id: 'ASC' }, }; case 'WORK_TYPE': return { priority: 'DESC', file: { work_type_id: direction }, id: 'ASC', + option_items: { id: 'ASC' }, }; default: // switchのcase漏れが発生した場合に型エラーになるようにする diff --git a/dictation_server/src/repositories/users/users.repository.service.ts b/dictation_server/src/repositories/users/users.repository.service.ts index e132ad2..e319156 100644 --- a/dictation_server/src/repositories/users/users.repository.service.ts +++ b/dictation_server/src/repositories/users/users.repository.service.ts @@ -1166,6 +1166,14 @@ export class UsersRepositoryService { option_items: true, }, }, + order: { + worktype: { + custom_worktype_id: 'ASC', + option_items: { + id: 'ASC', + }, + }, + }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, });