From dc52ec2022cf73827166c861278a83a6b2d42bd1 Mon Sep 17 00:00:00 2001 From: "makabe.t" Date: Thu, 22 Feb 2024 07:33:55 +0000 Subject: [PATCH 01/12] =?UTF-8?q?Merged=20PR=20765:=20=E3=83=87=E3=83=BC?= =?UTF-8?q?=E3=82=BF=E5=89=8A=E9=99=A4=E3=83=84=E3=83=BC=E3=83=AB=E4=BD=9C?= =?UTF-8?q?=E6=88=90=EF=BC=8B=E5=8B=95=E4=BD=9C=E7=A2=BA=E8=AA=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3569: データ削除ツール作成+動作確認](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3569) - ADB2Cからのユーザー削除が100件ごとにしか削除できていなかったので、修正しました。 - 取得が100件まででそのユーザーに対して削除処理をしていたので100件までの削除になっていました。 - 対応として、100件づつの削除をユーザーが全削除されるまで実行するようにしました。 ## レビューポイント - 対応方法として適切でしょうか? - ループで制限を設けていますが、MAX値として適切でしょうか? ## UIの変更 - なし ## 動作確認状況 - ローカルで順に実行できることを確認 - 実際の削除は別途develop環境で実施します。 --- data_migration_tools/server/package-lock.json | 12 +++--- .../src/features/delete/delete.controller.ts | 5 ++- .../src/features/delete/delete.service.ts | 28 ++++++++++---- .../src/gateways/adb2c/adb2c.service.ts | 18 ++++----- .../blobstorage/blobstorage.service.ts | 6 ++- .../delete/delete.repository.service.ts | 37 ++++++++++++++++++- 6 files changed, 80 insertions(+), 26 deletions(-) diff --git a/data_migration_tools/server/package-lock.json b/data_migration_tools/server/package-lock.json index 5987ef1..c1c2143 100644 --- a/data_migration_tools/server/package-lock.json +++ b/data_migration_tools/server/package-lock.json @@ -3107,9 +3107,9 @@ } }, "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "devOptional": true, "bin": { "acorn": "bin/acorn" @@ -12333,9 +12333,9 @@ } }, "acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "devOptional": true }, "acorn-import-assertions": { diff --git a/data_migration_tools/server/src/features/delete/delete.controller.ts b/data_migration_tools/server/src/features/delete/delete.controller.ts index 94567ca..ed81c5a 100644 --- a/data_migration_tools/server/src/features/delete/delete.controller.ts +++ b/data_migration_tools/server/src/features/delete/delete.controller.ts @@ -11,6 +11,7 @@ import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; import { Request } from "express"; import { DeleteService } from "./delete.service"; import { DeleteResponse } from "./types/types"; +import { makeContext } from "src/common/log"; @ApiTags("delete") @Controller("delete") @@ -33,7 +34,9 @@ export class DeleteController { }) @Post() async deleteData(): Promise<{}> { - await this.deleteService.deleteData(); + const context = makeContext("tool", "delete"); + + await this.deleteService.deleteData(context); return {}; } } diff --git a/data_migration_tools/server/src/features/delete/delete.service.ts b/data_migration_tools/server/src/features/delete/delete.service.ts index 9ade573..3055d5b 100644 --- a/data_migration_tools/server/src/features/delete/delete.service.ts +++ b/data_migration_tools/server/src/features/delete/delete.service.ts @@ -3,6 +3,7 @@ import { DeleteRepositoryService } from "../../repositories/delete/delete.reposi import { makeErrorResponse } from "../../common/errors/makeErrorResponse"; import { AdB2cService } from "../../gateways/adb2c/adb2c.service"; import { BlobstorageService } from "../../gateways/blobstorage/blobstorage.service"; +import { Context } from "../../common/log"; @Injectable() export class DeleteService { @@ -17,21 +18,34 @@ export class DeleteService { * データを削除する * @returns data */ - async deleteData(): Promise { - this.logger.log(`[IN] ${this.deleteData.name}`); + async deleteData(context: Context): Promise { + this.logger.log( + `[IN] [${context.getTrackingId()}] ${this.deleteData.name}` + ); try { // BlobStorageからデータを削除する - await this.blobstorageService.deleteContainers(); + await this.blobstorageService.deleteContainers(context); - // ADB2Cからユーザ情報を取得する - const users = await this.adB2cService.getUsers(); - const externalIds = users.map((user) => user.id); - await this.adB2cService.deleteUsers(externalIds); + // 100件ずつのユーザー取得なのですべて削除するまでループする + for (let i = 0; i < 500; i++) { + // ADB2Cからユーザ情報を取得する + const { users, hasNext } = await this.adB2cService.getUsers(context); + + // ユーザーがいない場合はループを抜ける + if (!hasNext) { + break; + } + + const externalIds = users.map((user) => user.id); + await this.adB2cService.deleteUsers(context, externalIds); + } // データベースからデータを削除する await this.deleteRepositoryService.deleteData(); // AutoIncrementの値をリセットする await this.deleteRepositoryService.resetAutoIncrement(); + // 初期データを挿入する + await this.deleteRepositoryService.insertInitData(context); } catch (e) { this.logger.error(`error=${e}`); if (e instanceof Error) { diff --git a/data_migration_tools/server/src/gateways/adb2c/adb2c.service.ts b/data_migration_tools/server/src/gateways/adb2c/adb2c.service.ts index b561f42..5ba0f0e 100644 --- a/data_migration_tools/server/src/gateways/adb2c/adb2c.service.ts +++ b/data_migration_tools/server/src/gateways/adb2c/adb2c.service.ts @@ -30,14 +30,10 @@ export const isConflictError = (arg: unknown): arg is ConflictError => { export class AdB2cService { private readonly logger = new Logger(AdB2cService.name); private readonly tenantName: string; - private readonly flowName: string; - private readonly ttl: number; private graphClient: Client; constructor(private readonly configService: ConfigService) { this.tenantName = this.configService.getOrThrow("TENANT_NAME"); - this.flowName = this.configService.getOrThrow("SIGNIN_FLOW_NAME"); - this.ttl = this.configService.getOrThrow("ADB2C_CACHE_TTL"); // ADB2Cへの認証情報 const credential = new ClientSecretCredential( @@ -111,8 +107,10 @@ export class AdB2cService { * @param externalIds * @returns users */ - async getUsers(): Promise { - this.logger.log(`[IN] ${this.getUsers.name}`); + async getUsers( + context: Context + ): Promise<{ users: AdB2cUser[]; hasNext: boolean }> { + this.logger.log(`[IN] [${context.getTrackingId()}] ${this.getUsers.name}`); try { const res: AdB2cResponse = await this.graphClient @@ -121,7 +119,7 @@ export class AdB2cService { .filter(`creationType eq 'LocalAccount'`) .get(); - return res.value; + return { users: res.value, hasNext: !!res["@odata.nextLink"] }; } catch (e) { this.logger.error(`error=${e}`); const { statusCode } = e; @@ -177,9 +175,11 @@ export class AdB2cService { * Azure AD B2Cからユーザ情報を削除する(複数) * @param externalIds 外部ユーザーID */ - async deleteUsers(externalIds: string[]): Promise { + async deleteUsers(context: Context, externalIds: string[]): Promise { this.logger.log( - `[IN]${this.deleteUsers.name} | params: { externalIds: ${externalIds} };` + `[IN] [${context.getTrackingId()}] ${ + this.deleteUsers.name + } | params: { externalIds: ${externalIds} };` ); try { diff --git a/data_migration_tools/server/src/gateways/blobstorage/blobstorage.service.ts b/data_migration_tools/server/src/gateways/blobstorage/blobstorage.service.ts index 7c26388..ee603c6 100644 --- a/data_migration_tools/server/src/gateways/blobstorage/blobstorage.service.ts +++ b/data_migration_tools/server/src/gateways/blobstorage/blobstorage.service.ts @@ -89,8 +89,10 @@ export class BlobstorageService { * すべてのコンテナを削除します。 * @returns containers */ - async deleteContainers(): Promise { - this.logger.log(`[IN] ${this.deleteContainers.name}`); + async deleteContainers(context: Context): Promise { + this.logger.log( + `[IN] [${context.getTrackingId()}] ${this.deleteContainers.name}` + ); try { for await (const container of this.blobServiceClientAU.listContainers({ diff --git a/data_migration_tools/server/src/repositories/delete/delete.repository.service.ts b/data_migration_tools/server/src/repositories/delete/delete.repository.service.ts index 52539c0..f436207 100644 --- a/data_migration_tools/server/src/repositories/delete/delete.repository.service.ts +++ b/data_migration_tools/server/src/repositories/delete/delete.repository.service.ts @@ -1,11 +1,15 @@ import { Injectable } from "@nestjs/common"; import { DataSource } from "typeorm"; import { logger } from "@azure/identity"; -import { Account } from "./entity/account.entity"; import { AUTO_INCREMENT_START } from "../../constants"; +import { Term } from "./entity/term.entity"; +import { insertEntities } from "../../common/repository"; +import { Context } from "../../common/log"; @Injectable() export class DeleteRepositoryService { + // クエリログにコメントを出力するかどうか + private readonly isCommentOut = process.env.STAGE !== "local"; constructor(private dataSource: DataSource) {} /** @@ -54,4 +58,35 @@ export class DeleteRepositoryService { await queryRunner.release(); } } + + /** + * 初期データを挿入する + * @returns data + */ + async insertInitData(context: Context): Promise { + await this.dataSource.transaction(async (entityManager) => { + const termRepo = entityManager.getRepository(Term); + + // ワークフローのデータ作成 + const newTarmDpa = new Term(); + newTarmDpa.document_type = "DPA"; + newTarmDpa.version = "V0.1"; + const newTarmEula = new Term(); + newTarmEula.document_type = "EULA"; + newTarmEula.version = "V0.1"; + const newTarmPrivacyNotice = new Term(); + newTarmPrivacyNotice.document_type = "PrivacyNotice"; + newTarmPrivacyNotice.version = "V0.1"; + + const initTerms = [newTarmDpa, newTarmEula, newTarmPrivacyNotice]; + + await insertEntities( + Term, + termRepo, + initTerms, + this.isCommentOut, + context + ); + }); + } } From c31bb47bb8a18b12ff234a9f4aba5381a832d83a Mon Sep 17 00:00:00 2001 From: "maruyama.t" Date: Thu, 22 Feb 2024 07:52:16 +0000 Subject: [PATCH 02/12] =?UTF-8?q?Merged=20PR=20773:=20api=E3=81=ABtransfer?= =?UTF-8?q?=E3=81=8C=E5=AD=98=E5=9C=A8=E3=81=97=E3=81=AA=E3=81=8B=E3=81=A3?= =?UTF-8?q?=E3=81=9F=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3570: データ変換ツール(きれいなデータ版)作成+動作確認](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3570) - 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず) - 何をどう変更したか、追加したライブラリなど - このPull Requestでの対象/対象外 - 影響範囲(他の機能にも影響があるか) ## レビューポイント - 特にレビューしてほしい箇所 - 軽微なものや自明なものは記載不要 - 修正範囲が大きい場合などに記載 - 全体的にや仕様を満たしているか等は本当に必要な時のみ記載 ## UIの変更 - Before/Afterのスクショなど - スクショ置き場 ## 動作確認状況 - ローカルで確認、develop環境で確認など ## 補足 - 相談、参考資料などがあれば --- data_migration_tools/server/src/app.module.ts | 21 ++++++++++++++++--- .../features/transfer/transfer.controller.ts | 10 ++++----- .../src/features/transfer/transfer.module.ts | 10 ++++----- .../src/features/transfer/transfer.service.ts | 4 ++-- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/data_migration_tools/server/src/app.module.ts b/data_migration_tools/server/src/app.module.ts index 8089b78..6935bba 100644 --- a/data_migration_tools/server/src/app.module.ts +++ b/data_migration_tools/server/src/app.module.ts @@ -24,7 +24,9 @@ import { DeleteModule } from "./features/delete/delete.module"; import { DeleteRepositoryModule } from "./repositories/delete/delete.repository.module"; import { DeleteController } from "./features/delete/delete.controller"; import { DeleteService } from "./features/delete/delete.service"; - +import { TransferModule } from "./features/transfer/transfer.module"; +import { TransferController } from "./features/transfer/transfer.controller"; +import { TransferService } from "./features/transfer/transfer.service"; @Module({ imports: [ ServeStaticModule.forRoot({ @@ -37,6 +39,7 @@ import { DeleteService } from "./features/delete/delete.service"; AdB2cModule, AccountsModule, UsersModule, + TransferModule, RegisterModule, AccountsRepositoryModule, UsersRepositoryModule, @@ -61,8 +64,20 @@ import { DeleteService } from "./features/delete/delete.service"; inject: [ConfigService], }), ], - controllers: [RegisterController, AccountsController, UsersController, DeleteController], - providers: [RegisterService, AccountsService, UsersService, DeleteService], + controllers: [ + RegisterController, + AccountsController, + UsersController, + DeleteController, + TransferController, + ], + providers: [ + RegisterService, + AccountsService, + UsersService, + DeleteService, + TransferService, + ], }) export class AppModule { configure(consumer: MiddlewareConsumer) { diff --git a/data_migration_tools/server/src/features/transfer/transfer.controller.ts b/data_migration_tools/server/src/features/transfer/transfer.controller.ts index 8ac9923..59f90fc 100644 --- a/data_migration_tools/server/src/features/transfer/transfer.controller.ts +++ b/data_migration_tools/server/src/features/transfer/transfer.controller.ts @@ -11,7 +11,7 @@ import fs from "fs"; import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; import { Request } from "express"; import { transferRequest, transferResponse } from "./types/types"; -import { transferService } from "./transfer.service"; +import { TransferService } from "./transfer.service"; import { makeContext } from "../../common/log"; import { csvInputFile } from "../../common/types/types"; import { makeErrorResponse } from "src/common/errors/makeErrorResponse"; @@ -24,12 +24,12 @@ import { LICENSE_ALLOCATED_STATUS, USER_ROLES, AUTO_INCREMENT_START, -} from "../../../src/constants"; +} from "../../constants"; @ApiTags("transfer") @Controller("transfer") -export class transferController { - private readonly logger = new Logger(transferController.name); - constructor(private readonly transferService: transferService) {} +export class TransferController { + private readonly logger = new Logger(TransferController.name); + constructor(private readonly transferService: TransferService) {} @Post() @ApiResponse({ diff --git a/data_migration_tools/server/src/features/transfer/transfer.module.ts b/data_migration_tools/server/src/features/transfer/transfer.module.ts index b2a7893..d2d9695 100644 --- a/data_migration_tools/server/src/features/transfer/transfer.module.ts +++ b/data_migration_tools/server/src/features/transfer/transfer.module.ts @@ -1,9 +1,9 @@ import { Module } from "@nestjs/common"; -import { transferController } from "./transfer.controller"; -import { transferService } from "./transfer.service"; +import { TransferController } from "./transfer.controller"; +import { TransferService } from "./transfer.service"; @Module({ imports: [], - controllers: [transferController], - providers: [transferService], + controllers: [TransferController], + providers: [TransferService], }) -export class transferModule {} +export class TransferModule {} diff --git a/data_migration_tools/server/src/features/transfer/transfer.service.ts b/data_migration_tools/server/src/features/transfer/transfer.service.ts index 70b5d26..24f93de 100644 --- a/data_migration_tools/server/src/features/transfer/transfer.service.ts +++ b/data_migration_tools/server/src/features/transfer/transfer.service.ts @@ -22,9 +22,9 @@ import { registInputDataResponse } from "./types/types"; import fs from "fs"; @Injectable() -export class transferService { +export class TransferService { constructor() {} - private readonly logger = new Logger(transferService.name); + private readonly logger = new Logger(TransferService.name); /** * Regist Data From cb68c16eb89fd13d690cd1065ba089333609dd33 Mon Sep 17 00:00:00 2001 From: "maruyama.t" Date: Thu, 22 Feb 2024 08:23:31 +0000 Subject: [PATCH 03/12] =?UTF-8?q?Merged=20PR=20775:=20=E5=A4=89=E6=8F=9B?= =?UTF-8?q?=E3=83=84=E3=83=BC=E3=83=AB=E3=81=AE=E3=83=90=E3=83=AA=E3=83=87?= =?UTF-8?q?=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3=E3=83=81=E3=82=A7=E3=83=83?= =?UTF-8?q?=E3=82=AF=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3570: データ変換ツール(きれいなデータ版)作成+動作確認](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3570) - 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず) - 何をどう変更したか、追加したライブラリなど - このPull Requestでの対象/対象外 - 影響範囲(他の機能にも影響があるか) ## レビューポイント - 特にレビューしてほしい箇所 - 軽微なものや自明なものは記載不要 - 修正範囲が大きい場合などに記載 - 全体的にや仕様を満たしているか等は本当に必要な時のみ記載 ## UIの変更 - Before/Afterのスクショなど - スクショ置き場 ## 動作確認状況 - ローカルで確認、develop環境で確認など ## 補足 - 相談、参考資料などがあれば --- .../server/src/constants/index.ts | 2 +- .../features/transfer/transfer.controller.ts | 4 ++ .../src/features/transfer/transfer.service.ts | 64 +++++++++++-------- 3 files changed, 44 insertions(+), 26 deletions(-) diff --git a/data_migration_tools/server/src/constants/index.ts b/data_migration_tools/server/src/constants/index.ts index 4330bb6..71bd022 100644 --- a/data_migration_tools/server/src/constants/index.ts +++ b/data_migration_tools/server/src/constants/index.ts @@ -343,7 +343,7 @@ export const MIGRATION_TYPE = { export const COUNTRY_LIST = [ { value: "CA", label: "Canada" }, { value: "KY", label: "Cayman Islands" }, - { value: "US", label: "U.S.A." }, + { value: "US", label: "United States" }, { value: "AU", label: "Australia" }, { value: "NZ", label: "New Zealand" }, { value: "AT", label: "Austria" }, diff --git a/data_migration_tools/server/src/features/transfer/transfer.controller.ts b/data_migration_tools/server/src/features/transfer/transfer.controller.ts index 59f90fc..8725a4c 100644 --- a/data_migration_tools/server/src/features/transfer/transfer.controller.ts +++ b/data_migration_tools/server/src/features/transfer/transfer.controller.ts @@ -78,6 +78,10 @@ export class TransferController { let csvInputFile: csvInputFile[] = []; csvInputFileLines.forEach((line) => { const data = line.split(","); + // ダブルクォーテーションは削除 + data.forEach((value, index) => { + data[index] = value.replace(/"/g, ""); + }); csvInputFile.push({ type: data[0], account_id: data[1], diff --git a/data_migration_tools/server/src/features/transfer/transfer.service.ts b/data_migration_tools/server/src/features/transfer/transfer.service.ts index 24f93de..5a7a58a 100644 --- a/data_migration_tools/server/src/features/transfer/transfer.service.ts +++ b/data_migration_tools/server/src/features/transfer/transfer.service.ts @@ -173,6 +173,7 @@ export class TransferService { try { // dealerAccountIdを検索し、typeがCountryの場合 accountsOutputFileStep1.forEach((account) => { + console.log(account); if (account.type === MIGRATION_TYPE.COUNTRY) { // そのacccountIdをdealerAccountIdにもつアカウント(Distributor)を検索する const distributor = accountsOutputFileStep1.find( @@ -304,6 +305,7 @@ export class TransferService { if ( line.type !== MIGRATION_TYPE.ADMINISTRATOR && line.type !== MIGRATION_TYPE.BC && + line.type !== MIGRATION_TYPE.COUNTRY && line.type !== MIGRATION_TYPE.DISTRIBUTOR && line.type !== MIGRATION_TYPE.DEALER && line.type !== MIGRATION_TYPE.CUSTOMER && @@ -315,35 +317,47 @@ export class TransferService { ); } // countryのバリデーションチェック - if (!COUNTRY_LIST.find((country) => country.label === line.country)) { - throw new HttpException( - `country is invalid. index=${index} country=${line.country}`, - HttpStatus.BAD_REQUEST - ); + if (line.country) { + if (!COUNTRY_LIST.find((country) => country.label === line.country)) { + console.log(line.country); + throw new HttpException( + `country is invalid. index=${index} country=${line.country}`, + HttpStatus.BAD_REQUEST + ); + } } // mailのバリデーションチェック // メールアドレスの形式が正しいかどうかのチェック - const mailRegExp = new RegExp( - /^[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$/ - ); - if (!mailRegExp.test(line.email)) { - throw new HttpException( - `email is invalid. index=${index} email=${line.email}`, - HttpStatus.BAD_REQUEST - ); + const mailRegExp = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; + if (line.email) { + if (!mailRegExp.test(line.email)) { + throw new HttpException( + `email is invalid. index=${index} email=${line.email}`, + HttpStatus.BAD_REQUEST + ); + } } - // recording_modeのバリデーションチェック - // RECORDING_MODEに存在するかどうかのチェック - if ( - line.recording_mode !== RECORDING_MODE.DS2_QP && - line.recording_mode !== RECORDING_MODE.DS2_SP && - line.recording_mode !== RECORDING_MODE.DSS && - line.recording_mode !== null - ) { - throw new HttpException( - `recording_mode is invalid. index=${index} recording_mode=${line.recording_mode}`, - HttpStatus.BAD_REQUEST - ); + if (line.user_email) { + if (!mailRegExp.test(line.user_email)) { + throw new HttpException( + `user_email is invalid. index=${index} user_email=${line.email}`, + HttpStatus.BAD_REQUEST + ); + } + } + // recording_modeの値が存在する場合 + if (line.recording_mode) { + // recording_modeのバリデーションチェック + if ( + line.recording_mode !== RECORDING_MODE.DS2_QP && + line.recording_mode !== RECORDING_MODE.DS2_SP && + line.recording_mode !== RECORDING_MODE.DSS + ) { + throw new HttpException( + `recording_mode is invalid. index=${index} recording_mode=${line.recording_mode}`, + HttpStatus.BAD_REQUEST + ); + } } }); } catch (e) { From a65d6a277466beff9a2e92bc2b27dc9542bf02dc Mon Sep 17 00:00:00 2001 From: "maruyama.t" Date: Thu, 22 Feb 2024 08:40:57 +0000 Subject: [PATCH 04/12] =?UTF-8?q?Merged=20PR=20776:=20=E9=9A=8E=E5=B1=A4?= =?UTF-8?q?=E3=81=AE=E4=BB=98=E3=81=91=E6=9B=BF=E3=81=88=E3=82=92=E8=AA=A4?= =?UTF-8?q?=E3=81=A3=E3=81=A6=E3=81=84=E3=81=9F=E3=81=AE=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3570: データ変換ツール(きれいなデータ版)作成+動作確認](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3570) - 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず) - 何をどう変更したか、追加したライブラリなど - このPull Requestでの対象/対象外 - 影響範囲(他の機能にも影響があるか) ## レビューポイント - 特にレビューしてほしい箇所 - 軽微なものや自明なものは記載不要 - 修正範囲が大きい場合などに記載 - 全体的にや仕様を満たしているか等は本当に必要な時のみ記載 ## UIの変更 - Before/Afterのスクショなど - スクショ置き場 ## 動作確認状況 - ローカルで確認、develop環境で確認など ## 補足 - 相談、参考資料などがあれば --- .../src/features/transfer/transfer.service.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/data_migration_tools/server/src/features/transfer/transfer.service.ts b/data_migration_tools/server/src/features/transfer/transfer.service.ts index 5a7a58a..31b75ed 100644 --- a/data_migration_tools/server/src/features/transfer/transfer.service.ts +++ b/data_migration_tools/server/src/features/transfer/transfer.service.ts @@ -173,16 +173,17 @@ export class TransferService { try { // dealerAccountIdを検索し、typeがCountryの場合 accountsOutputFileStep1.forEach((account) => { - console.log(account); if (account.type === MIGRATION_TYPE.COUNTRY) { + console.log(account); // そのacccountIdをdealerAccountIdにもつアカウント(Distributor)を検索する const distributor = accountsOutputFileStep1.find( - (distributor) => - account.type === MIGRATION_TYPE.DISTRIBUTOR && - distributor.dealerAccountId === account.accountId + (distributor) => distributor.accountId === account.dealerAccountId ); - // DistributorのdealerAccountIdをBC(Countryの親)に付け替える - distributor.dealerAccountId = account.dealerAccountId; + console.log(distributor); + if (distributor) { + // DistributorのdealerAccountIdをBC(Countryの親)に付け替える + distributor.dealerAccountId = account.dealerAccountId; + } } }); // typeがCountryのアカウントを取り除く From f03342bc5519dc1cc12bc450271582d8f8215b2f Mon Sep 17 00:00:00 2001 From: "makabe.t" Date: Thu, 22 Feb 2024 08:45:20 +0000 Subject: [PATCH 05/12] =?UTF-8?q?Merged=20PR=20777:=20=E3=83=87=E3=83=BC?= =?UTF-8?q?=E3=82=BF=E5=89=8A=E9=99=A4=E3=83=84=E3=83=BC=E3=83=AB=E4=BD=9C?= =?UTF-8?q?=E6=88=90=EF=BC=8B=E5=8B=95=E4=BD=9C=E7=A2=BA=E8=AA=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3569: データ削除ツール作成+動作確認](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3569) - クライアントに不要なフォルダが残っていたので削除しました。 ## レビューポイント - 共有 ## UIの変更 - なし ## 動作確認状況 - ローカルで確認 --- .../client/src/common/error/code.ts | 70 ------------------- .../src/common/error/makeErrorResponse.ts | 10 --- .../client/src/common/error/message.ts | 59 ---------------- .../client/src/common/error/types/types.ts | 15 ---- 4 files changed, 154 deletions(-) delete mode 100644 data_migration_tools/client/src/common/error/code.ts delete mode 100644 data_migration_tools/client/src/common/error/makeErrorResponse.ts delete mode 100644 data_migration_tools/client/src/common/error/message.ts delete mode 100644 data_migration_tools/client/src/common/error/types/types.ts diff --git a/data_migration_tools/client/src/common/error/code.ts b/data_migration_tools/client/src/common/error/code.ts deleted file mode 100644 index 3c488da..0000000 --- a/data_migration_tools/client/src/common/error/code.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* -エラーコード作成方針 -E+6桁(数字)で構成する。 -- 1~2桁目の値は種類(業務エラー、システムエラー...) -- 3~4桁目の値は原因箇所(トークン、DB、...) -- 5~6桁目の値は任意の重複しない値 -ex) -E00XXXX : システムエラー(通信エラーやDB接続失敗など) -E01XXXX : 業務エラー -EXX00XX : 内部エラー(内部プログラムのエラー) -EXX01XX : トークンエラー(トークン認証関連) -EXX02XX : DBエラー(DB関連) -EXX03XX : ADB2Cエラー(DB関連) -*/ -export const ErrorCodes = [ - 'E009999', // 汎用エラー - 'E000101', // トークン形式不正エラー - 'E000102', // トークン有効期限切れエラー - 'E000103', // トークン非アクティブエラー - 'E000104', // トークン署名エラー - 'E000105', // トークン発行元エラー - 'E000106', // トークンアルゴリズムエラー - 'E000107', // トークン不足エラー - 'E000108', // トークン権限エラー - 'E000301', // ADB2Cへのリクエスト上限超過エラー - 'E000401', // IPアドレス未設定エラー - 'E000501', // リクエストID未設定エラー - 'E010001', // パラメータ形式不正エラー - 'E010201', // 未認証ユーザエラー - 'E010202', // 認証済ユーザエラー - 'E010203', // 管理ユーザ権限エラー - 'E010204', // ユーザ不在エラー - 'E010205', // DBのRoleが想定外の値エラー - 'E010206', // DBのTierが想定外の値エラー - 'E010207', // ユーザーのRole変更不可エラー - 'E010208', // ユーザーの暗号化パスワード不足エラー - 'E010209', // ユーザーの同意済み利用規約バージョンが最新でないエラー - 'E010301', // メールアドレス登録済みエラー - 'E010302', // authorId重複エラー - 'E010401', // PONumber重複エラー - 'E010501', // アカウント不在エラー - 'E010502', // アカウント情報変更不可エラー - 'E010503', // 代行操作不許可エラー - 'E010504', // アカウントロックエラー - 'E010601', // タスク変更不可エラー(タスクが変更できる状態でない、またはタスクが存在しない) - 'E010602', // タスク変更権限不足エラー - 'E010603', // タスク不在エラー - 'E010701', // Blobファイル不在エラー - 'E010801', // ライセンス不在エラー - 'E010802', // ライセンス取り込み済みエラー - 'E010803', // ライセンス発行済みエラー - 'E010804', // ライセンス不足エラー - 'E010805', // ライセンス有効期限切れエラー - 'E010806', // ライセンス割り当て不可エラー - 'E010807', // ライセンス割り当て解除済みエラー - 'E010808', // ライセンス注文キャンセル不可エラー - 'E010809', // ライセンス発行キャンセル不可エラー(ステータスが変えられている場合) - 'E010810', // ライセンス発行キャンセル不可エラー(発行から一定期間経過した場合) - 'E010811', // ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合) - 'E010812', // ライセンス未割当エラー - 'E010908', // タイピストグループ不在エラー - 'E010909', // タイピストグループ名重複エラー - 'E011001', // ワークタイプ重複エラー - 'E011002', // ワークタイプ登録上限超過エラー - 'E011003', // ワークタイプ不在エラー - 'E011004', // ワークタイプ使用中エラー - 'E012001', // テンプレートファイル不在エラー - 'E013001', // ワークフローのAuthorIDとWorktypeIDのペア重複エラー - 'E013002', // ワークフロー不在エラー -] as const; diff --git a/data_migration_tools/client/src/common/error/makeErrorResponse.ts b/data_migration_tools/client/src/common/error/makeErrorResponse.ts deleted file mode 100644 index 0a677b4..0000000 --- a/data_migration_tools/client/src/common/error/makeErrorResponse.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { errors } from './message'; -import { ErrorCodeType, ErrorResponse } from './types/types'; - -export const makeErrorResponse = (errorcode: ErrorCodeType): ErrorResponse => { - const msg = errors[errorcode]; - return { - code: errorcode, - message: msg, - }; -}; diff --git a/data_migration_tools/client/src/common/error/message.ts b/data_migration_tools/client/src/common/error/message.ts deleted file mode 100644 index 9383694..0000000 --- a/data_migration_tools/client/src/common/error/message.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Errors } from './types/types'; - -// エラーコードとメッセージ対応表 -export const errors: Errors = { - E009999: 'Internal Server Error.', - E000101: 'Token invalid format Error.', - E000102: 'Token expired Error.', - E000103: 'Token not before Error', - E000104: 'Token invalid signature Error.', - E000105: 'Token invalid issuer Error.', - E000106: 'Token invalid algorithm Error.', - E000107: 'Token is not exist Error.', - E000108: 'Token authority failed Error.', - E000301: 'ADB2C request limit exceeded Error', - E000401: 'IP address not found Error.', - E000501: 'Request ID not found Error.', - E010001: 'Param invalid format Error.', - E010201: 'Email not verified user Error.', - E010202: 'Email already verified user Error.', - E010203: 'Administrator Permissions Error.', - E010204: 'User not Found Error.', - E010205: 'Role from DB is unexpected value Error.', - E010206: 'Tier from DB is unexpected value Error.', - E010207: 'User role change not allowed Error.', - E010208: 'User encryption password not found Error.', - E010209: 'Accepted term not latest Error.', - E010301: 'This email user already created Error', - E010302: 'This AuthorId already used Error', - E010401: 'This PoNumber already used Error', - E010501: 'Account not Found Error.', - E010502: 'Account information cannot be changed Error.', - E010503: 'Delegation not allowed Error.', - E010504: 'Account is locked Error.', - E010601: 'Task is not Editable Error', - E010602: 'No task edit permissions Error', - E010603: 'Task not found Error.', - E010701: 'File not found in Blob Storage Error.', - E010801: 'License not exist Error', - E010802: 'License already activated Error', - E010803: 'License already issued Error', - E010804: 'License shortage Error', - E010805: 'License is expired Error', - E010806: 'License is unavailable Error', - E010807: 'License is already deallocated Error', - E010808: 'Order cancel failed Error', - E010809: 'Already license order status changed Error', - E010810: 'Cancellation period expired error', - E010811: 'Already license allocated Error', - E010812: 'License not allocated Error', - E010908: 'Typist Group not exist Error', - E010909: 'Typist Group name already exist Error', - E011001: 'This WorkTypeID already used Error', - E011002: 'WorkTypeID create limit exceeded Error', - E011003: 'WorkTypeID not found Error', - E011004: 'WorkTypeID is in use Error', - E012001: 'Template file not found Error', - E013001: 'AuthorId and WorktypeId pair already exists Error', - E013002: 'Workflow not found Error', -}; diff --git a/data_migration_tools/client/src/common/error/types/types.ts b/data_migration_tools/client/src/common/error/types/types.ts deleted file mode 100644 index 8746924..0000000 --- a/data_migration_tools/client/src/common/error/types/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { ErrorCodes } from '../code'; - -export class ErrorResponse { - @ApiProperty() - message: string; - @ApiProperty() - code: string; -} - -export type ErrorCodeType = (typeof ErrorCodes)[number]; - -export type Errors = { - [P in ErrorCodeType]: string; -}; From fbdfeee73cee400281d8b839f304e12ca6ffadcf Mon Sep 17 00:00:00 2001 From: "makabe.t" Date: Thu, 22 Feb 2024 11:42:31 +0000 Subject: [PATCH 06/12] =?UTF-8?q?Merged=20PR=20778:=20=E5=89=8A=E9=99=A4?= =?UTF-8?q?=E3=83=84=E3=83=BC=E3=83=AB=E3=81=AE=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3788: 削除ツールの修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3788) - ADB2Cの削除に失敗するので対応しました。 - 最後のページの場合に削除より先に抜けていたので、削除処理を先にやるように修正しました。 ## レビューポイント - 共有 ## UIの変更 - なし ## 動作確認状況 - developで確認 --- .../server/src/features/delete/delete.service.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/data_migration_tools/server/src/features/delete/delete.service.ts b/data_migration_tools/server/src/features/delete/delete.service.ts index 3055d5b..3644220 100644 --- a/data_migration_tools/server/src/features/delete/delete.service.ts +++ b/data_migration_tools/server/src/features/delete/delete.service.ts @@ -12,7 +12,7 @@ export class DeleteService { private readonly deleteRepositoryService: DeleteRepositoryService, private readonly blobstorageService: BlobstorageService, private readonly adB2cService: AdB2cService - ) {} + ) { } /** * データを削除する @@ -31,13 +31,14 @@ export class DeleteService { // ADB2Cからユーザ情報を取得する const { users, hasNext } = await this.adB2cService.getUsers(context); - // ユーザーがいない場合はループを抜ける - if (!hasNext) { - break; - } const externalIds = users.map((user) => user.id); await this.adB2cService.deleteUsers(context, externalIds); + + // 削除していないユーザーがいない場合はループを抜ける + if (!hasNext) { + break; + } } // データベースからデータを削除する From b524fd59957364010ab17ee56cf0ca300e10d160 Mon Sep 17 00:00:00 2001 From: "saito.k" Date: Fri, 23 Feb 2024 00:42:05 +0000 Subject: [PATCH 07/12] =?UTF-8?q?Merged=20PR=20768:=20U-105=E3=83=A1?= =?UTF-8?q?=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=81=AE=E3=83=89=E3=82=A4?= =?UTF-8?q?=E3=83=84=E8=AA=9E=E9=83=A8=E5=88=86=E3=81=8C=E8=8B=B1=E8=AA=9E?= =?UTF-8?q?=E3=81=AB=E3=81=AA=E3=81=A3=E3=81=A6=E3=81=84=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3770: U-105メッセージのドイツ語部分が英語になっている](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3770) - U-105のメール文面を修正 - ドイツ語の文章であるべき箇所が英語になっていた ## レビューポイント - 特になし ## UIの変更 - Before/Afterのスクショなど - スクショ置き場 ## 動作確認状況 - ローカルで確認 ## 補足 - 相談、参考資料などがあれば --- .../src/templates/template_U_105.html | 26 ++++++++----------- .../src/templates/template_U_105.txt | 18 ++++++------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/dictation_server/src/templates/template_U_105.html b/dictation_server/src/templates/template_U_105.html index f71ac41..8290aab 100644 --- a/dictation_server/src/templates/template_U_105.html +++ b/dictation_server/src/templates/template_U_105.html @@ -33,30 +33,26 @@

<Deutsch>

-

Dear $CUSTOMER_NAME$,

+

Sehr geehrte(r) $CUSTOMER_NAME$,

- We have received your requested license order.
- - Number of licenses ordered: $LICENSE_QUANTITY$
- - PO Number: $PO_NUMBER$ + Wir haben Ihre gewünschte Lizenzbestellung erhalten.
+ - Anzahl der bestellten Lizenzen: $LICENSE_QUANTITY$
+ - Bestellnummer: $PO_NUMBER$

- Licenses will be issued by your $DEALER_NAME$ which you have selected in - the setting. Licenses issued by your dealer will be stored in your - license inventory. Please log in to the ODMS Cloud to view and assign - licenses to your users. + Die Lizenzen werden von Ihrem $DEALER_NAME$ ausgestellt, den Sie in den Einstellungen ausgewählt haben. + Von Ihrem Händler ausgestellte Lizenzen werden in Ihrem Lizenzbestand gespeichert. + Bitte melden Sie sich bei der ODMS Cloud an, um Lizenzen anzuzeigen und Ihren Benutzern zuzuweisen.

- Licenses are valid for 12 months from the date they are assigned to a - user. + Lizenzen sind ab dem Datum, an dem sie einem Benutzer zugewiesen wurden, 12 Monate lang gültig.

- If you need support regarding ODMS Cloud, please contact $DEALER_NAME$. + Wenn Sie Unterstützung bezüglich ODMS Cloud benötigen, wenden Sie sich bitte an $DEALER_NAME$.

- If you have received this e-mail in error, please delete this e-mail - from your system.
- This is an automatically generated e-mail and this mailbox is not - monitored. Please do not reply. + Wenn Sie diese E-Mail fälschlicherweise erhalten haben, löschen Sie diese E-Mail bitte aus Ihrem System.
+ Dies ist eine automatisch generierte E-Mail und dieses Postfach wird nicht überwacht. Bitte nicht antworten.

diff --git a/dictation_server/src/templates/template_U_105.txt b/dictation_server/src/templates/template_U_105.txt index 1fa5b81..c569f9f 100644 --- a/dictation_server/src/templates/template_U_105.txt +++ b/dictation_server/src/templates/template_U_105.txt @@ -17,20 +17,20 @@ This is an automatically generated e-mail and this mailbox is not monitored. P -Dear $CUSTOMER_NAME$, +Sehr geehrte(r) $CUSTOMER_NAME$, -We have received your requested license order. - - Number of licenses ordered: $LICENSE_QUANTITY$ - - PO Number: $PO_NUMBER$ +Wir haben Ihre gewünschte Lizenzbestellung erhalten. + - Anzahl der bestellten Lizenzen: $LICENSE_QUANTITY$ + - Bestellnummer: $PO_NUMBER$ -Licenses will be issued by your $DEALER_NAME$ which you have selected in the setting. Licenses issued by your dealer will be stored in your license inventory. Please log in to the ODMS Cloud to view and assign licenses to your users. +Die Lizenzen werden von Ihrem $DEALER_NAME$ ausgestellt, den Sie in den Einstellungen ausgewählt haben. Von Ihrem Händler ausgestellte Lizenzen werden in Ihrem Lizenzbestand gespeichert. Bitte melden Sie sich bei der ODMS Cloud an, um Lizenzen anzuzeigen und Ihren Benutzern zuzuweisen. -Licenses are valid for 12 months from the date they are assigned to a user. +Lizenzen sind ab dem Datum, an dem sie einem Benutzer zugewiesen wurden, 12 Monate lang gültig. -If you need support regarding ODMS Cloud, please contact $DEALER_NAME$. +Wenn Sie Unterstützung bezüglich ODMS Cloud benötigen, wenden Sie sich bitte an $DEALER_NAME$. -If you have received this e-mail in error, please delete this e-mail from your system. -This is an automatically generated e-mail and this mailbox is not monitored. Please do not reply. +Wenn Sie diese E-Mail fälschlicherweise erhalten haben, löschen Sie diese E-Mail bitte aus Ihrem System. +Dies ist eine automatisch generierte E-Mail und dieses Postfach wird nicht überwacht. Bitte nicht antworten. From 68d1a1796bd6f5e40b4f26a34e8b5c101f84958d Mon Sep 17 00:00:00 2001 From: masaaki Date: Mon, 26 Feb 2024 08:59:37 +0000 Subject: [PATCH 08/12] =?UTF-8?q?Merged=20PR=20783:=20[1=E5=9B=9E=E7=9B=AE?= =?UTF-8?q?=E5=AE=9F=E8=A1=8C]=E5=AE=9F=E6=96=BD=E5=BE=8C=E3=81=AE?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=AE=9F=E6=96=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3790: [1回目実行]実施後の修正実施](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3790) - 移行データ動作確認(1回目)で発生した不具合の対応を実施しました  ・バリデータでエラーとなる(コメントアウトして実行したところ、成功)  ・アカウント管理者のユーザーのメール認証がfalseで登録されるので、   パスワード変更では認証をできない⇒強制敵にtrueで登録する  ・(指摘外、検証ツール実装時に内部検出)カードライセンス登録時、カードライセンス発行・ライセンステーブルも登録する - このPull Requestでの対象/対象外  上記以外の指摘(下記)はタスク3772にて対応するため本プルリク対象外  変換ツール   ・ディーラーアカウントに登録されたアカウントがいないCSVで    変換するとディーラーがundefined(パラメータがない)状態でJSON出力されてしまう。   ・重複したメールアドレスの取り込みが未実装   ・有効期限が9999/~は移行対象外とする   ・ワークタイプの出力がされない  登録ツール   ・ファイルパスの取り扱いが変換ツールと異なる - 影響範囲(他の機能にも影響があるか) ## レビューポイント - カードライセンス登録時のライセンスのアカウントIDについて、第一階層アカウントのため「AUTO_INCREMENT_START: 853211」を設定しているが問題ないか?  →移行データ上第一階層アカウントは最初に登場するため問題ない認識 ## UIの変更 - 無し ## 動作確認状況 - ローカルで確認済 ## 補足 - 相談、参考資料などがあれば --- .../server/src/common/types/types.ts | 2 +- .../accounts/accounts.repository.service.ts | 17 ++--- .../licenses/entity/license.entity.ts | 27 +++++++ .../licenses/licenses.repository.module.ts | 7 +- .../licenses/licenses.repository.service.ts | 75 +++++++++++++++---- 5 files changed, 102 insertions(+), 26 deletions(-) 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 {}; }); } - } From d0628caa05433c6d7c674a57bf66cd9df784a84b Mon Sep 17 00:00:00 2001 From: "saito.k" Date: Mon, 26 Feb 2024 10:42:26 +0000 Subject: [PATCH 09/12] =?UTF-8?q?Merged=20PR=20767:=20=E3=83=91=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=83=8A=E3=83=BC=E8=BF=BD=E5=8A=A0=E6=88=90=E5=8A=9F?= =?UTF-8?q?=E6=99=82=E3=81=AB=E4=B8=80=E8=A6=A7=E3=81=AE=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E3=81=8C=E8=A1=8C=E3=82=8F=E3=82=8C=E3=81=A6=E3=81=84=E3=81=AA?= =?UTF-8?q?=E3=81=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3769: パートナー追加成功時に一覧の更新が行われていない](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3769) - パートナー追加成功時に一覧の更新を行う ## レビューポイント - 特になし ## UIの変更 - https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task3769?csf=1&web=1&e=ajJOBd ## 動作確認状況 - ローカルで確認、develop環境で確認など ## 補足 - 相談、参考資料などがあれば --- .../pages/PartnerPage/addPartnerAccountPopup.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) 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(); } }, [ From 5a78a6668f90ee77a100f25cce45c84cb270dd9b Mon Sep 17 00:00:00 2001 From: "saito.k" Date: Mon, 26 Feb 2024 11:29:22 +0000 Subject: [PATCH 10/12] =?UTF-8?q?Merged=20PR=20769:=20/users/relations?= =?UTF-8?q?=E3=83=AC=E3=82=B9=E3=83=9D=E3=83=B3=E3=82=B9=E3=81=AEWorkTypeI?= =?UTF-8?q?D=E3=82=92ID=E5=90=8D=E3=81=AE=E6=98=87=E9=A0=86=E3=81=AB?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3783: /users/relationsレスポンスのWorkTypeIDをID名の昇順にする](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3783) - relationsAPIレスポンスのWorkTypeList内の順番を指定する - テスト修正 ## レビューポイント - 修正内容に不足はないか ## UIの変更 - Before/Afterのスクショなど - スクショ置き場 ## 動作確認状況 - ローカルで確認 ## 補足 - 相談、参考資料などがあれば --- .../common/validators/authorId.validator.ts | 2 +- .../src/features/users/users.service.spec.ts | 29 ++++++++++++------- .../users/users.repository.service.ts | 8 +++++ 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/dictation_server/src/common/validators/authorId.validator.ts b/dictation_server/src/common/validators/authorId.validator.ts index 23afafb..a790edc 100644 --- a/dictation_server/src/common/validators/authorId.validator.ts +++ b/dictation_server/src/common/validators/authorId.validator.ts @@ -15,7 +15,7 @@ import { USER_ROLES } from '../../constants'; @ValidatorConstraint({ name: 'IsAuthorId', async: false }) export class IsAuthorId implements ValidatorConstraintInterface { validate(value: any, args: ValidationArguments) { - const request = args.object as SignupRequest | PostUpdateUserRequest; + const request = args.object as SignupRequest | PostUpdateUserRequest; // requestの存在チェック if (!request) { return false; diff --git a/dictation_server/src/features/users/users.service.spec.ts b/dictation_server/src/features/users/users.service.spec.ts index 8e3cbf5..bb4931c 100644 --- a/dictation_server/src/features/users/users.service.spec.ts +++ b/dictation_server/src/features/users/users.service.spec.ts @@ -170,7 +170,7 @@ describe('UsersService.confirmUser', () => { }); expect(_subject).toBe('Account Registered Notification [U-101]'); expect(_url).toBe('http://localhost:8081/'); - }, 600000); + }); it('トークンの形式が不正な場合、形式不正エラーとなる。', async () => { if (!source) fail(); @@ -2735,17 +2735,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); @@ -2754,15 +2758,17 @@ 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'); @@ -2778,14 +2784,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/users/users.repository.service.ts b/dictation_server/src/repositories/users/users.repository.service.ts index 8c39441..9a70a96 100644 --- a/dictation_server/src/repositories/users/users.repository.service.ts +++ b/dictation_server/src/repositories/users/users.repository.service.ts @@ -918,6 +918,14 @@ export class UsersRepositoryService { option_items: true, }, }, + order: { + worktype: { + custom_worktype_id: 'ASC', + option_items: { + id: 'ASC', + }, + }, + }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, }); From 0ab6488f58890239d21400bdb663de7690a99e06 Mon Sep 17 00:00:00 2001 From: "saito.k" Date: Mon, 26 Feb 2024 11:48:02 +0000 Subject: [PATCH 11/12] =?UTF-8?q?Merged=20PR=20779:=20=E3=82=BF=E3=82=B9?= =?UTF-8?q?=E3=82=AF=E4=B8=80=E8=A6=A7=E7=94=BB=E9=9D=A2=E3=81=AEOptionIte?= =?UTF-8?q?m=E3=81=8C=E3=82=BD=E3=83=BC=E3=83=88=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=E3=81=AB=E3=82=88=E3=81=A3=E3=81=A6=E8=A1=A8=E7=A4=BA=E9=A0=86?= =?UTF-8?q?=E3=81=8C=E3=81=8A=E3=81=8B=E3=81=97=E3=81=8F=E3=81=AA=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3787: タスク一覧画面のOptionItemがソート条件によって表示順がおかしくなる](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3787) - タスク一覧取得APIレスポンスにあるOptionItemの順番を固定する(idの昇順) - テスト修正 ## レビューポイント - 特になし ## 動作確認状況 - ローカルで確認 ## 補足 - 相談、参考資料などがあれば --- .../src/features/tasks/tasks.service.spec.ts | 97 +++++++++++++++++++ .../tasks/tasks.repository.service.ts | 13 +++ 2 files changed, 110 insertions(+) diff --git a/dictation_server/src/features/tasks/tasks.service.spec.ts b/dictation_server/src/features/tasks/tasks.service.spec.ts index 0b77c95..1531077 100644 --- a/dictation_server/src/features/tasks/tasks.service.spec.ts +++ b/dictation_server/src/features/tasks/tasks.service.spec.ts @@ -782,6 +782,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/repositories/tasks/tasks.repository.service.ts b/dictation_server/src/repositories/tasks/tasks.repository.service.ts index a95a8ab..e2bbefa 100644 --- a/dictation_server/src/repositories/tasks/tasks.repository.service.ts +++ b/dictation_server/src/repositories/tasks/tasks.repository.service.ts @@ -1462,78 +1462,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漏れが発生した場合に型エラーになるようにする From f0d71937e336af153dd07716cdd8a2c14439d5e6 Mon Sep 17 00:00:00 2001 From: "maruyama.t" Date: Tue, 27 Feb 2024 06:24:41 +0000 Subject: [PATCH 12/12] =?UTF-8?q?Merged=20PR=20780:=20=E3=83=87=E3=83=BC?= =?UTF-8?q?=E3=82=BF=E5=A4=89=E6=8F=9B=E3=83=84=E3=83=BC=E3=83=AB=EF=BC=88?= =?UTF-8?q?=E6=B1=9A=E3=81=84=E3=83=87=E3=83=BC=E3=82=BF=E5=AF=BE=E5=BF=9C?= =?UTF-8?q?=E7=89=88=EF=BC=89=E3=81=AE=E4=BD=9C=E6=88=90=EF=BC=8B=E5=8B=95?= =?UTF-8?q?=E4=BD=9C=E7=A2=BA=E8=AA=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3776: データ変換ツール(汚いデータ対応版)の作成](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3776) 綺麗なデータ対応版のレビュー指摘も合わせて修正。 一旦OMDS様よりいただいた1万件~のデータを処理できることは確認済みです。 実装コストとバグの入れ込みを懸念し、有効期限が"9999/12/31"のデータは最初にデータを積む段階で除外するようにしました。 ## レビューポイント - メールアドレス重複チェックについて、想定通りの重複対象を検索出来ているか。 - step3の1.アカウントとユーザが同じ場合 adminMainとuserEmailが重複していた場合に、重複していたユーザーは削除し、アカウントのみを残す(accountユーザーのroleとauthorIdは削除したuserに設定されていたものとする)処理は妥当か。 →accountのIFにroleとauthorIdを追加し、register側のcreateAccountで登録するようにしています。 ## 動作確認状況 - ローカルで確認(Account_transition_2024.1.19.csvで実施) 4つのJSONファイルができていることを確認。 Countryの場合の付け替えができていることを確認。 adminMainとemailが重複している場合の重複削除ができていることを確認。 ## 補足 - 登録ツールと共通のパラメータで動作するようにしました。 例) POST: localhost:8280/transfer Body: { "inputFilePath": "./data/" }  変換ツールの使い方としてはAccount_transition.jsonというファイルを見るようにしています。   --- .../server/src/common/types/types.ts | 97 ++-- .../server/src/constants/index.ts | 2 +- .../src/features/accounts/accounts.service.ts | 2 + .../features/register/register.controller.ts | 109 ++-- .../src/features/register/register.service.ts | 21 +- .../features/transfer/transfer.controller.ts | 84 ++- .../src/features/transfer/transfer.service.ts | 538 ++++++++++++------ .../src/features/transfer/types/types.ts | 26 +- .../accounts/accounts.repository.service.ts | 3 + .../licenses/licenses.repository.service.ts | 62 +- .../worktypes/worktypes.repository.service.ts | 10 +- 11 files changed, 601 insertions(+), 353 deletions(-) diff --git a/data_migration_tools/server/src/common/types/types.ts b/data_migration_tools/server/src/common/types/types.ts index de92211..69ef466 100644 --- a/data_migration_tools/server/src/common/types/types.ts +++ b/data_migration_tools/server/src/common/types/types.ts @@ -8,8 +8,8 @@ export class csvInputFile { last_name: string; country: string; state: string; - start_date: Date; - expired_date: Date; + start_date: string; + expired_date: string; user_email: string; author_id: string; recording_mode: string; @@ -34,7 +34,7 @@ export class csvInputFile { wt19: string; wt20: string; } -export class AccountsOutputFileStep1 { +export class AccountsFileType { accountId: number; type: string; companyName: string; @@ -43,9 +43,11 @@ export class AccountsOutputFileStep1 { adminName: string; adminMail: string; userId: number; + role: string; + authorId: string; } -export class AccountsOutputFile { +export class AccountsFile { accountId: number; type: number; companyName: string; @@ -54,18 +56,11 @@ export class AccountsOutputFile { adminName: string; adminMail: string; userId: number; + role: string; + authorId: string; } -export class AccountsInputFile { - accountId: number; - type: number; - companyName: string; - country: string; - dealerAccountId?: number; - adminName: string; - adminMail: string; - userId: number; -} -export class UsersOutputFile { + +export class UsersFile { accountId: number; userId: number; name: string; @@ -74,23 +69,7 @@ export class UsersOutputFile { email: string; } -export class UsersInputFile { - accountId: number; - userId: number; - name: string; - role: string; - authorId: string; - email: string; -} - -export class LicensesOutputFile { - expiry_date: string; - account_id: number; - type: string; - status: string; - allocated_user_id?: number; -} -export class LicensesInputFile { +export class LicensesFile { expiry_date: string; account_id: number; type: string; @@ -98,16 +77,12 @@ export class LicensesInputFile { allocated_user_id?: number; } -export class WorktypesOutputFile { - account_id: number; - custom_worktype_id: string; -} -export class WorktypesInputFile { +export class WorktypesFile { account_id: number; custom_worktype_id: string; } -export class CardLicensesInputFile { +export class CardLicensesFile { license_id: number; issue_id: number; card_license_key: string; @@ -118,10 +93,10 @@ export class CardLicensesInputFile { updated_by?: string; } -export function isAccountsInputFileArray(obj: any): obj is AccountsInputFile[] { - return Array.isArray(obj) && obj.every((item) => isAccountsInputFile(item)); +export function isAccountsFileArray(obj: any): obj is AccountsFile[] { + return Array.isArray(obj) && obj.every((item) => isAccountsFile(item)); } -export function isAccountsInputFile(obj: any): obj is AccountsInputFile { +export function isAccountsFile(obj: any): obj is AccountsFile { return ( typeof obj === "object" && obj !== null && @@ -141,14 +116,20 @@ export function isAccountsInputFile(obj: any): obj is AccountsInputFile { "adminMail" in obj && typeof obj.adminMail === "string" && "userId" in obj && - typeof obj.userId === "number" + typeof obj.userId === "number" && + ("role" in obj + ? obj.role === null || typeof obj.role === "string" + : true) && + ("authorId" in obj + ? obj.authorId === null || typeof obj.authorId === "string" + : true) ); } -export function isUsersInputFileArray(obj: any): obj is UsersInputFile[] { - return Array.isArray(obj) && obj.every((item) => isUsersInputFile(item)); +export function isUsersFileArray(obj: any): obj is UsersFile[] { + return Array.isArray(obj) && obj.every((item) => isUsersFile(item)); } -export function isUsersInputFile(obj: any): obj is UsersInputFile { +export function isUsersFile(obj: any): obj is UsersFile { return ( typeof obj === "object" && obj !== null && @@ -167,10 +148,10 @@ export function isUsersInputFile(obj: any): obj is UsersInputFile { ); } -export function isLicensesInputFileArray(obj: any): obj is LicensesInputFile[] { - return Array.isArray(obj) && obj.every((item) => isLicensesInputFile(item)); +export function isLicensesFileArray(obj: any): obj is LicensesFile[] { + return Array.isArray(obj) && obj.every((item) => isLicensesFile(item)); } -export function isLicensesInputFile(obj: any): obj is LicensesInputFile { +export function isLicensesFile(obj: any): obj is LicensesFile { return ( typeof obj === "object" && obj !== null && @@ -187,12 +168,10 @@ export function isLicensesInputFile(obj: any): obj is LicensesInputFile { ); } -export function isWorktypesInputFileArray( - obj: any -): obj is WorktypesInputFile[] { - return Array.isArray(obj) && obj.every((item) => isWorktypesInputFile(item)); +export function isWorktypesFileArray(obj: any): obj is WorktypesFile[] { + return Array.isArray(obj) && obj.every((item) => isWorktypesFile(item)); } -export function isWorktypesInputFile(obj: any): obj is WorktypesInputFile { +export function isWorktypesFile(obj: any): obj is WorktypesFile { return ( typeof obj === "object" && obj !== null && @@ -203,16 +182,10 @@ export function isWorktypesInputFile(obj: any): obj is WorktypesInputFile { ); } -export function isCardLicensesInputFileArray( - obj: any -): obj is CardLicensesInputFile[] { - return ( - Array.isArray(obj) && obj.every((item) => isCardLicensesInputFile(item)) - ); +export function isCardLicensesFileArray(obj: any): obj is CardLicensesFile[] { + return Array.isArray(obj) && obj.every((item) => isCardLicensesFile(item)); } -export function isCardLicensesInputFile( - obj: any -): obj is CardLicensesInputFile { +export function isCardLicensesFile(obj: any): obj is CardLicensesFile { return ( typeof obj === "object" && obj !== null && diff --git a/data_migration_tools/server/src/constants/index.ts b/data_migration_tools/server/src/constants/index.ts index 71bd022..74e6e3a 100644 --- a/data_migration_tools/server/src/constants/index.ts +++ b/data_migration_tools/server/src/constants/index.ts @@ -351,7 +351,7 @@ export const COUNTRY_LIST = [ { value: "BG", label: "Bulgaria" }, { value: "HR", label: "Croatia" }, { value: "CY", label: "Cyprus" }, - { value: "CZ", label: "Czech Republic" }, + { value: "CZ", label: "Czech" }, { value: "DK", label: "Denmark" }, { value: "EE", label: "Estonia" }, { value: "FI", label: "Finland" }, diff --git a/data_migration_tools/server/src/features/accounts/accounts.service.ts b/data_migration_tools/server/src/features/accounts/accounts.service.ts index 4a7dd65..2954cbd 100644 --- a/data_migration_tools/server/src/features/accounts/accounts.service.ts +++ b/data_migration_tools/server/src/features/accounts/accounts.service.ts @@ -37,6 +37,7 @@ export class AccountsService { password: string, username: string, role: string, + authorId: string, acceptedEulaVersion: string, acceptedPrivacyNoticeVersion: string, acceptedDpaVersion: string, @@ -103,6 +104,7 @@ export class AccountsService { type, externalUser.sub, role, + authorId, accountId, userId, acceptedEulaVersion, diff --git a/data_migration_tools/server/src/features/register/register.controller.ts b/data_migration_tools/server/src/features/register/register.controller.ts index c55d548..cb36d2a 100644 --- a/data_migration_tools/server/src/features/register/register.controller.ts +++ b/data_migration_tools/server/src/features/register/register.controller.ts @@ -17,11 +17,11 @@ import { AccountsService } from "../accounts/accounts.service"; import { UsersService } from "../users/users.service"; import { makeContext } from "../../common/log"; import { - isAccountsInputFileArray, - isUsersInputFileArray, - isLicensesInputFileArray, - isWorktypesInputFileArray, - isCardLicensesInputFileArray, + isAccountsFileArray, + isUsersFileArray, + isLicensesFileArray, + isWorktypesFileArray, + isCardLicensesFileArray, } from "../../common/types/types"; import { makePassword } from "../../common/password/password"; import { @@ -73,13 +73,24 @@ export class RegisterController { const cardLicensesFileFullPath = inputFilePath + "cardLicenses.json"; // ファイル存在チェックと読み込み - if ( - !fs.existsSync(accouncsFileFullPath) || - !fs.existsSync(usersFileFullPath) || - !fs.existsSync(licensesFileFullPath) || - !fs.existsSync(worktypesFileFullPath) || - !fs.existsSync(cardLicensesFileFullPath) - ) { + // どのファイルがないのかわからないのでそれぞれに存在しない場合はエラーを出す + if (!fs.existsSync(accouncsFileFullPath)) { + this.logger.error(`file not exists from ${inputFilePath}`); + throw new Error(`file not exists from ${inputFilePath}`); + } + if (!fs.existsSync(usersFileFullPath)) { + this.logger.error(`file not exists from ${inputFilePath}`); + throw new Error(`file not exists from ${inputFilePath}`); + } + if (!fs.existsSync(licensesFileFullPath)) { + this.logger.error(`file not exists from ${inputFilePath}`); + throw new Error(`file not exists from ${inputFilePath}`); + } + if (!fs.existsSync(worktypesFileFullPath)) { + this.logger.error(`file not exists from ${inputFilePath}`); + throw new Error(`file not exists from ${inputFilePath}`); + } + if (!fs.existsSync(cardLicensesFileFullPath)) { this.logger.error(`file not exists from ${inputFilePath}`); throw new Error(`file not exists from ${inputFilePath}`); } @@ -90,34 +101,50 @@ export class RegisterController { ); // 型ガード(account) - if (!isAccountsInputFileArray(accountsObject)) { - throw new Error("input file is not accountsInputFiles"); + if (!isAccountsFileArray(accountsObject)) { + throw new Error("input file is not AccountsFiles"); } - for (const accountsInputFile of accountsObject) { + for (const AccountsFile of accountsObject) { // ランダムなパスワードを生成する const ramdomPassword = makePassword(); + // roleの設定 + // roleの値がnullなら"none"、null以外ならroleの値、 + // また、roleの値が"author"なら"author"を設定 + let role: string; + let authorId: string; + if (AccountsFile.role === null) { + role = USER_ROLES.NONE; + authorId = null; + } else if (AccountsFile.role === USER_ROLES.AUTHOR) { + role = USER_ROLES.AUTHOR; + authorId = AccountsFile.authorId; + } else { + // ありえないが、roleの値が"none"または"author"の文字列以外の場合はエラーを返す + throw new Error("Invalid role value"); + } await this.accountsService.createAccount( context, - accountsInputFile.companyName, - accountsInputFile.country, - accountsInputFile.dealerAccountId, - accountsInputFile.adminMail, + AccountsFile.companyName, + AccountsFile.country, + AccountsFile.dealerAccountId, + AccountsFile.adminMail, ramdomPassword, - accountsInputFile.adminName, - "none", + AccountsFile.adminName, + role, + authorId, null, null, null, - accountsInputFile.type, - accountsInputFile.accountId, - accountsInputFile.userId + AccountsFile.type, + AccountsFile.accountId, + AccountsFile.userId ); // ratelimit対応のためsleepを行う await sleep(MIGRATION_DATA_REGISTER_INTERVAL_MILLISEC); } - // const accountsInputFiles = accountsObject as AccountsInputFile[]; + // const AccountsFiles = accountsObject as AccountsFile[]; // ユーザの登録用ファイル読み込み const usersObject = JSON.parse( @@ -125,24 +152,24 @@ export class RegisterController { ); // 型ガード(user) - if (!isUsersInputFileArray(usersObject)) { - throw new Error("input file is not usersInputFiles"); + if (!isUsersFileArray(usersObject)) { + throw new Error("input file is not UsersFiles"); } - for (const usersInputFile of usersObject) { - this.logger.log(usersInputFile.name); + for (const UsersFile of usersObject) { + this.logger.log(UsersFile.name); await this.usersService.createUser( context, - usersInputFile.name, - usersInputFile.role === USER_ROLES.AUTHOR + UsersFile.name, + UsersFile.role === USER_ROLES.AUTHOR ? USER_ROLES.AUTHOR : USER_ROLES.NONE, - usersInputFile.email, + UsersFile.email, true, true, - usersInputFile.accountId, - usersInputFile.userId, - usersInputFile.authorId, + UsersFile.accountId, + UsersFile.userId, + UsersFile.authorId, false, null, true @@ -157,8 +184,8 @@ export class RegisterController { ); // 型ガード(license) - if (!isLicensesInputFileArray(licensesObject)) { - throw new Error("input file is not licensesInputFiles"); + if (!isLicensesFileArray(licensesObject)) { + throw new Error("input file is not LicensesFiles"); } // ワークタイプの登録用ファイル読み込み @@ -167,8 +194,8 @@ export class RegisterController { ); // 型ガード(Worktypes) - if (!isWorktypesInputFileArray(worktypesObject)) { - throw new Error("input file is not WorktypesInputFiles"); + if (!isWorktypesFileArray(worktypesObject)) { + throw new Error("input file is not WorktypesFiles"); } // カードライセンスの登録用ファイル読み込み @@ -177,8 +204,8 @@ export class RegisterController { ); // 型ガード(cardLicenses) - if (!isCardLicensesInputFileArray(cardLicensesObject)) { - throw new Error("input file is not cardLicensesInputFiles"); + if (!isCardLicensesFileArray(cardLicensesObject)) { + throw new Error("input file is not cardLicensesFiles"); } // ライセンス・ワークタイプ・カードライセンスの登録 diff --git a/data_migration_tools/server/src/features/register/register.service.ts b/data_migration_tools/server/src/features/register/register.service.ts index 6e42dc1..9af654f 100644 --- a/data_migration_tools/server/src/features/register/register.service.ts +++ b/data_migration_tools/server/src/features/register/register.service.ts @@ -1,9 +1,9 @@ import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common"; import { Context } from "../../common/log"; import { - LicensesInputFile, - WorktypesInputFile, - CardLicensesInputFile, + LicensesFile, + WorktypesFile, + CardLicensesFile, } from "../../common/types/types"; import { LicensesRepositoryService } from "../../repositories/licenses/licenses.repository.service"; import { WorktypesRepositoryService } from "../../repositories/worktypes/worktypes.repository.service"; @@ -22,9 +22,9 @@ export class RegisterService { */ async registLicenseAndWorktypeData( context: Context, - licensesInputFiles: LicensesInputFile[], - worktypesInputFiles: WorktypesInputFile[], - cardlicensesInputFiles: CardLicensesInputFile[] + LicensesFiles: LicensesFile[], + WorktypesFiles: WorktypesFile[], + cardLicensesFiles: CardLicensesFile[] ): Promise { // パラメータ内容が長大なのでログには出さない this.logger.log( @@ -35,20 +35,17 @@ export class RegisterService { try { this.logger.log("Licenses register start"); - await this.licensesRepository.insertLicenses(context, licensesInputFiles); + await this.licensesRepository.insertLicenses(context, LicensesFiles); this.logger.log("Licenses register end"); this.logger.log("Worktypes register start"); - await this.worktypesRepository.createWorktype( - context, - worktypesInputFiles - ); + await this.worktypesRepository.createWorktype(context, WorktypesFiles); this.logger.log("Worktypes register end"); this.logger.log("CardLicenses register start"); await this.licensesRepository.insertCardLicenses( context, - cardlicensesInputFiles + cardLicensesFiles ); this.logger.log("CardLicenses register end"); } catch (e) { diff --git a/data_migration_tools/server/src/features/transfer/transfer.controller.ts b/data_migration_tools/server/src/features/transfer/transfer.controller.ts index 8725a4c..7916f27 100644 --- a/data_migration_tools/server/src/features/transfer/transfer.controller.ts +++ b/data_migration_tools/server/src/features/transfer/transfer.controller.ts @@ -15,16 +15,7 @@ import { TransferService } from "./transfer.service"; import { makeContext } from "../../common/log"; import { csvInputFile } from "../../common/types/types"; import { makeErrorResponse } from "src/common/errors/makeErrorResponse"; -import { - COUNTRY_LIST, - MIGRATION_TYPE, - TIERS, - WORKTYPE_MAX_COUNT, - RECORDING_MODE, - LICENSE_ALLOCATED_STATUS, - USER_ROLES, - AUTO_INCREMENT_START, -} from "../../constants"; +import { AUTO_INCREMENT_START } from "../../constants"; @ApiTags("transfer") @Controller("transfer") export class TransferController { @@ -57,16 +48,16 @@ export class TransferController { ); try { // 読み込みファイルのフルパス - const csvFileFullPath = inputFilePath + ".csv"; + const accouncsFileFullPath = inputFilePath + "Account_transition.csv"; // ファイル存在チェックと読み込み - if (!fs.existsSync(csvFileFullPath)) { + if (!fs.existsSync(accouncsFileFullPath)) { this.logger.error(`file not exists from ${inputFilePath}`); throw new Error(`file not exists from ${inputFilePath}`); } // CSVファイルを全行読み込む - const inputFile = fs.readFileSync(csvFileFullPath, "utf-8"); + const inputFile = fs.readFileSync(accouncsFileFullPath, "utf-8"); // レコードごとに分割 const csvInputFileLines = inputFile.split("\n"); @@ -77,11 +68,34 @@ export class TransferController { // 項目ごとに切り分ける let csvInputFile: csvInputFile[] = []; csvInputFileLines.forEach((line) => { + // 項目にカンマが入っている場合を考慮して、ダブルクォーテーションで囲まれた部分を一つの項目として扱う + const regExp = /"[^"]*"/g; + const matchList = line.match(regExp); + if (matchList) { + matchList.forEach((match) => { + const replaced = match.replace(/,/g, " "); + line = line.replace(match, replaced); + }); + } const data = line.split(","); - // ダブルクォーテーションは削除 + // ダブルクォーテーションを削除 data.forEach((value, index) => { data[index] = value.replace(/"/g, ""); }); + // "\r"を削除 + data[data.length - 1] = data[data.length - 1].replace(/\r/g, ""); + // dataの要素数が34(csvInputFileの要素数)より多い場合、フォーマット不一致エラー(移行元はworktypeの数が20より多く設定できるので理論上は存在する) + // worktypeの数の確認を促すエラーを出す + if (data.length > 34) { + this.logger.error( + `[${context.getTrackingId()}] format error.please check the number of elements in worktype. data=${data}` + ); + throw new HttpException( + makeErrorResponse("E009999"), + HttpStatus.BAD_REQUEST + ); + } + csvInputFile.push({ type: data[0], account_id: data[1], @@ -92,8 +106,8 @@ export class TransferController { last_name: data[6], country: data[7], state: data[8], - start_date: new Date(data[9]), - expired_date: new Date(data[10]), + start_date: data[9], + expired_date: data[10], user_email: data[11], author_id: data[12], recording_mode: data[13], @@ -119,6 +133,10 @@ export class TransferController { wt20: data[33], }); }); + // 最後の行がundefinedの場合はその行を削除 + if (csvInputFile[csvInputFile.length - 1].account_id === undefined) { + csvInputFile.pop(); + } // 各データのバリデーションチェック await this.transferService.validateInputData(context, csvInputFile); @@ -132,35 +150,39 @@ export class TransferController { accountIdMap.set(accountId, index + AUTO_INCREMENT_START); }); // CSVファイルの変換 - const transferResponse = await this.transferService.registInputData( + const transferResponseCsv = await this.transferService.transferInputData( context, csvInputFile, accountIdMap ); // countryを除いた階層の再配置 - const accountsOutputFileStep1Lines = - transferResponse.accountsOutputFileStep1Lines; - const accountsOutputFile = await this.transferService.relocateHierarchy( + const AccountsFileTypeLines = transferResponseCsv.accountsFileTypeLines; + const AccountsFile = await this.transferService.relocateHierarchy( context, - accountsOutputFileStep1Lines + AccountsFileTypeLines ); + const UsersFile = transferResponseCsv.usersFileLines; + const LicensesFile = transferResponseCsv.licensesFileLines; // メールアドレスの重複を削除 - // デモライセンスの削除 - // いったんこのままコミットしてテストを実施する + const resultDuplicateEmail = + await this.transferService.removeDuplicateEmail( + context, + AccountsFile, + UsersFile, + LicensesFile + ); - // transferResponseを4つのJSONファイルの出力する(出力先はinputと同じにする) + // transferResponseCsvを4つのJSONファイルの出力する(出力先はinputと同じにする) const outputFilePath = body.inputFilePath; - const usersOutputFile = transferResponse.usersOutputFileLines; - const licensesOutputFile = transferResponse.licensesOutputFileLines; - const worktypesOutputFile = transferResponse.worktypesOutputFileLines; + const WorktypesFile = transferResponseCsv.worktypesFileLines; this.transferService.outputJsonFile( context, outputFilePath, - accountsOutputFile, - usersOutputFile, - licensesOutputFile, - worktypesOutputFile + resultDuplicateEmail.accountsFileLines, + resultDuplicateEmail.usersFileLines, + resultDuplicateEmail.licensesFileLines, + WorktypesFile ); return {}; } catch (e) { diff --git a/data_migration_tools/server/src/features/transfer/transfer.service.ts b/data_migration_tools/server/src/features/transfer/transfer.service.ts index 31b75ed..c1b0757 100644 --- a/data_migration_tools/server/src/features/transfer/transfer.service.ts +++ b/data_migration_tools/server/src/features/transfer/transfer.service.ts @@ -1,12 +1,12 @@ import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common"; import { Context } from "../../common/log"; import { - AccountsOutputFileStep1, - UsersOutputFile, - LicensesOutputFile, - WorktypesOutputFile, + AccountsFileType, + UsersFile, + LicensesFile, + WorktypesFile, csvInputFile, - AccountsOutputFile, + AccountsFile, } from "../../common/types/types"; import { COUNTRY_LIST, @@ -18,8 +18,12 @@ import { USER_ROLES, SWITCH_FROM_TYPE, } from "src/constants"; -import { registInputDataResponse } from "./types/types"; +import { + registInputDataResponse, + removeDuplicateEmailResponse, +} from "./types/types"; import fs from "fs"; +import { makeErrorResponse } from "src/common/error/makeErrorResponse"; @Injectable() export class TransferService { @@ -27,33 +31,33 @@ export class TransferService { private readonly logger = new Logger(TransferService.name); /** - * Regist Data + * Transfer Input Data * @param OutputFilePath: string * @param csvInputFile: csvInputFile[] */ - async registInputData( + async transferInputData( context: Context, csvInputFile: csvInputFile[], accountIdMap: Map ): Promise { // パラメータ内容が長大なのでログには出さない this.logger.log( - `[IN] [${context.getTrackingId()}] ${this.registInputData.name}` + `[IN] [${context.getTrackingId()}] ${this.transferInputData.name}` ); try { - let accountsOutputFileStep1Lines: AccountsOutputFileStep1[] = []; - let usersOutputFileLines: UsersOutputFile[] = []; - let licensesOutputFileLines: LicensesOutputFile[] = []; - let worktypesOutputFileLines: WorktypesOutputFile[] = []; + let accountsFileTypeLines: AccountsFileType[] = []; + let usersFileLines: UsersFile[] = []; + let licensesFileLines: LicensesFile[] = []; + let worktypesFileLines: WorktypesFile[] = []; let userIdIndex = 0; + // authorIdとuserIdの対応関係を保持するMapを定義 + const authorIdToUserIdMap: Map = new Map(); // csvInputFileを一行読み込みする csvInputFile.forEach((line) => { // typeが"USER"以外の場合、アカウントデータの作成を行う if (line.type !== MIGRATION_TYPE.USER) { - // userIdのインクリメント - userIdIndex = userIdIndex + 1; // line.countryの値を読み込みCOUNTRY_LISTのlabelからvalueに変換する const country = COUNTRY_LIST.find( (country) => country.label === line.country @@ -71,8 +75,16 @@ export class TransferService { if (line.parent_id) { parentAccountId = accountIdMap.get(line.parent_id); } - // AccountsOutputFile配列にPush - accountsOutputFileStep1Lines.push({ + // 万が一parent_idが入力されているのに存在しなかった場合は、nullを設定する。 + if (parentAccountId === undefined) { + parentAccountId = null; + } + + // userIdIndexをインクリメントする + userIdIndex++; + + // AccountsFile配列にPush + accountsFileTypeLines.push({ // accountIdはaccountIdMapから取得する accountId: accountIdMap.get(line.account_id), type: line.type, @@ -82,150 +94,176 @@ export class TransferService { adminName: adminName, adminMail: line.email, userId: userIdIndex, + role: null, + authorId: null, }); } else { - // typeが"USER"の場合、ユーザデータの作成を行う - // userIdのインクリメント - userIdIndex = userIdIndex + 1; - // nameの変換 - // もしline.last_nameとline.first_nameが存在しない場合、line.emailをnameにする - // 存在する場合は、last_name + " " + first_name - let name = line.email; - if (line.last_name && line.first_name) { - name = `${line.last_name} ${line.first_name}`; - } - // roleの変換 - // authorIdが設定されてる場合はauthor、されていない場合は移行しないので次の行に進む - if (line.author_id) { - usersOutputFileLines.push({ - accountId: accountIdMap.get(line.account_id), - userId: userIdIndex, - name: name, - role: USER_ROLES.AUTHOR, - authorId: line.author_id, - email: line.user_email, - }); - } else { - return; - } - // ライセンスのデータの作成を行う - // authorIdが設定されてる場合、statusは"allocated"、allocated_user_idは対象のユーザID - // されていない場合、statusは"reusable"、allocated_user_idはnull - licensesOutputFileLines.push({ - expiry_date: line.expired_date.toISOString(), - account_id: accountIdMap.get(line.account_id), - type: SWITCH_FROM_TYPE.NONE, - status: line.author_id - ? LICENSE_ALLOCATED_STATUS.ALLOCATED - : LICENSE_ALLOCATED_STATUS.REUSABLE, - allocated_user_id: line.author_id ? userIdIndex : null, - }); - // WorktypesOutputFileの作成 - // wt1~wt20まで読み込み、account単位で作成する - // 作成したWorktypesOutputFileを配列にPush - for (let i = 1; i <= WORKTYPE_MAX_COUNT; i++) { - const wt = `wt${i}`; - if (line[wt]) { - // 既に存在する場合は、作成しない - if ( - worktypesOutputFileLines.find( - (worktype) => - worktype.account_id === accountIdMap.get(line.account_id) && - worktype.custom_worktype_id === line[wt].toString() - ) - ) { - continue; + // typeが"USER"の場合 + if (line.type == MIGRATION_TYPE.USER) { + // line.author_idが存在する場合のみユーザーデータを作成する + if (line.author_id) { + // userIdIndexをインクリメントする + userIdIndex++; + + // nameの変換 + // もしline.last_nameとline.first_nameが存在しない場合、line.emailをnameにする + // 存在する場合は、last_name + " " + first_name + let name = line.user_email; + if (line.last_name && line.first_name) { + name = `${line.last_name} ${line.first_name}`; + } + // UsersFileの作成 + usersFileLines.push({ + accountId: accountIdMap.get(line.account_id), + userId: userIdIndex, + name: name, + role: USER_ROLES.AUTHOR, + authorId: line.author_id, + email: line.user_email, + }); + // authorIdとuserIdの対応関係をマッピング + authorIdToUserIdMap.set(line.author_id, userIdIndex); + } + + // ライセンスのデータの作成を行う + // line.expired_dateが9999/12/31 23:59:59.997のデータの場合はデモライセンスなので登録しない + if (line.expired_date !== "9999/12/31 23:59:59.997") { + // authorIdが設定されてる場合、statusは"allocated"、allocated_user_idは対象のユーザID + // されていない場合、statusは"reusable"、allocated_user_idはnull + let status: string; + let allocated_user_id: number | null; + if (line.author_id) { + status = LICENSE_ALLOCATED_STATUS.ALLOCATED; + allocated_user_id = + authorIdToUserIdMap.get(line.author_id) ?? null; // authorIdに対応するuserIdを取得 + } else { + status = LICENSE_ALLOCATED_STATUS.REUSABLE; + allocated_user_id = null; + } + // LicensesFileの作成 + licensesFileLines.push({ + expiry_date: line.expired_date, + account_id: accountIdMap.get(line.account_id), + type: SWITCH_FROM_TYPE.NONE, + status: status, + allocated_user_id: allocated_user_id, + }); + } + // WorktypesFileの作成 + // wt1~wt20まで読み込み、account単位で作成する + // 作成したWorktypesFileを配列にPush + for (let i = 1; i <= WORKTYPE_MAX_COUNT; i++) { + const wt = `wt${i}`; + if (line[wt]) { + // account_idで同一のcustom_worktype_idが存在しない場合は、作成する + if ( + !worktypesFileLines.find( + (worktype) => + worktype.account_id === + accountIdMap.get(line.account_id) && + worktype.custom_worktype_id === line[wt] + ) + ) { + worktypesFileLines.push({ + account_id: accountIdMap.get(line.account_id), + custom_worktype_id: line[wt], + }); + } else { + continue; + } } } } } - // つぎの行に進む }); return { - accountsOutputFileStep1Lines, - usersOutputFileLines, - licensesOutputFileLines, - worktypesOutputFileLines, + accountsFileTypeLines, + usersFileLines, + licensesFileLines, + worktypesFileLines, }; } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); + throw new HttpException( + makeErrorResponse("E009999"), + HttpStatus.INTERNAL_SERVER_ERROR + ); } finally { this.logger.log( - `[OUT] [${context.getTrackingId()}] ${this.registInputData.name}` + `[OUT] [${context.getTrackingId()}] ${this.transferInputData.name}` ); } } /** * 階層の付け替えを行う - * @param accountsOutputFileStep1: AccountsOutputFileStep1[] - * @returns AccountsOutputFile[] + * @param accountsFileType: AccountsFileType[] + * @returns AccountsFile[] */ async relocateHierarchy( context: Context, - accountsOutputFileStep1: AccountsOutputFileStep1[] - ): Promise { + accountsFileType: AccountsFileType[] + ): Promise { // パラメータ内容が長大なのでログには出さない this.logger.log( `[IN] [${context.getTrackingId()}] ${this.relocateHierarchy.name}` ); try { - // dealerAccountIdを検索し、typeがCountryの場合 - accountsOutputFileStep1.forEach((account) => { - if (account.type === MIGRATION_TYPE.COUNTRY) { - console.log(account); - // そのacccountIdをdealerAccountIdにもつアカウント(Distributor)を検索する - const distributor = accountsOutputFileStep1.find( - (distributor) => distributor.accountId === account.dealerAccountId - ); - console.log(distributor); - if (distributor) { - // DistributorのdealerAccountIdをBC(Countryの親)に付け替える - distributor.dealerAccountId = account.dealerAccountId; - } - } - }); - // typeがCountryのアカウントを取り除く - accountsOutputFileStep1 = accountsOutputFileStep1.filter( - (account) => account.type !== MIGRATION_TYPE.COUNTRY - ); + const relocatedAccounts: AccountsFile[] = []; + const countryRecords: Map = new Map(); - // typeをtierに変換し、AccountsOutputFileに変換する - let accountsOutputFile: AccountsOutputFile[] = []; - accountsOutputFileStep1.forEach((account) => { - let tier = 0; - switch (account.type) { - case MIGRATION_TYPE.ADMINISTRATOR: - tier = TIERS.TIER1; - break; - case MIGRATION_TYPE.BC: - tier = TIERS.TIER2; - break; - case MIGRATION_TYPE.DISTRIBUTOR: - tier = TIERS.TIER3; - break; - case MIGRATION_TYPE.DEALER: - tier = TIERS.TIER4; - break; - case MIGRATION_TYPE.CUSTOMER: - tier = TIERS.TIER5; - break; + // accountsFileTypeをループ + accountsFileType.forEach((account) => { + // Countryの場合はDistributorのアカウントIDと新たな親アカウントID(BC)の組み合わせをMapに登録 + if (account.type === MIGRATION_TYPE.COUNTRY) { + // 配下のDistributorアカウントを取得 + const distributor = accountsFileType.find( + (distributor) => + distributor.dealerAccountId === account.accountId && + distributor.type === MIGRATION_TYPE.DISTRIBUTOR + ); + if (distributor) { + countryRecords.set(distributor.accountId, account.dealerAccountId); + } + } else { + // Country以外のアカウントの場合は、そのまま登録 + countryRecords.set(account.accountId, account.dealerAccountId); } - accountsOutputFile.push({ - accountId: account.accountId, - type: tier, - companyName: account.companyName, - country: account.country, - dealerAccountId: account.dealerAccountId, - adminName: account.adminName, - adminMail: account.adminMail, - userId: account.userId, - }); }); - return accountsOutputFile; + + // AccountsFileTypeのループを行い、階層情報の置換と新たな配列へのpushを行う + accountsFileType.forEach((account) => { + // Countryのレコードは除外する + if (account.type !== MIGRATION_TYPE.COUNTRY) { + const dealerAccountId = + countryRecords.get(account.dealerAccountId) ?? + account.dealerAccountId; + const type = this.getAccountType(account.type); + const newAccount: AccountsFile = { + accountId: account.accountId, + type: type, + companyName: account.companyName, + country: account.country, + dealerAccountId: dealerAccountId, + adminName: account.adminName, + adminMail: account.adminMail, + userId: account.userId, + role: account.role, + authorId: account.authorId, + }; + + relocatedAccounts.push(newAccount); + } + }); + + return relocatedAccounts; } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); + throw new HttpException( + makeErrorResponse("E009999"), + HttpStatus.INTERNAL_SERVER_ERROR + ); } finally { this.logger.log( `[OUT] [${context.getTrackingId()}] ${this.relocateHierarchy.name}` @@ -233,21 +271,38 @@ export class TransferService { } } + // メソッド: アカウントタイプを数値に変換するヘルパー関数 + private getAccountType(type: string): number { + switch (type) { + case MIGRATION_TYPE.ADMINISTRATOR: + return TIERS.TIER1; + case MIGRATION_TYPE.BC: + return TIERS.TIER2; + case MIGRATION_TYPE.DISTRIBUTOR: + return TIERS.TIER3; + case MIGRATION_TYPE.DEALER: + return TIERS.TIER4; + case MIGRATION_TYPE.CUSTOMER: + return TIERS.TIER5; + default: + return 0; + } + } /** * JSONファイルの出力 * @param outputFilePath: string - * @param accountsOutputFile: AccountsOutputFile[] - * @param usersOutputFile: UsersOutputFile[] - * @param licensesOutputFile: LicensesOutputFile[] - * @param worktypesOutputFile: WorktypesOutputFile[] + * @param accountsFile: AccountsFile[] + * @param usersFile: UsersFile[] + * @param licensesFile: LicensesFile[] + * @param worktypesFile: WorktypesFile[] */ async outputJsonFile( context: Context, outputFilePath: string, - accountsOutputFile: AccountsOutputFile[], - usersOutputFile: UsersOutputFile[], - licensesOutputFile: LicensesOutputFile[], - worktypesOutputFile: WorktypesOutputFile[] + accountsFile: AccountsFile[], + usersFile: UsersFile[], + licensesFile: LicensesFile[], + worktypesFile: WorktypesFile[] ): Promise { // パラメータ内容が長大なのでログには出さない this.logger.log( @@ -256,29 +311,24 @@ export class TransferService { try { // JSONファイルの出力を行う - // accountsOutputFile配列の出力 - const accountsOutputFileJson = JSON.stringify(accountsOutputFile); - fs.writeFileSync( - `${outputFilePath}_accounts.json`, - accountsOutputFileJson - ); - // usersOutputFile - const usersOutputFileJson = JSON.stringify(usersOutputFile); - fs.writeFileSync(`${outputFilePath}_users.json`, usersOutputFileJson); - // licensesOutputFile - const licensesOutputFileJson = JSON.stringify(licensesOutputFile); - fs.writeFileSync( - `${outputFilePath}_licenses.json`, - licensesOutputFileJson - ); - // worktypesOutputFile - const worktypesOutputFileJson = JSON.stringify(worktypesOutputFile); - fs.writeFileSync( - `${outputFilePath}_worktypes.json`, - worktypesOutputFileJson - ); + // AccountsFile配列の出力 + const accountsFileJson = JSON.stringify(accountsFile); + fs.writeFileSync(`${outputFilePath}accounts.json`, accountsFileJson); + // UsersFile + const usersFileJson = JSON.stringify(usersFile); + fs.writeFileSync(`${outputFilePath}users.json`, usersFileJson); + // LicensesFile + const licensesFileJson = JSON.stringify(licensesFile); + fs.writeFileSync(`${outputFilePath}licenses.json`, licensesFileJson); + // WorktypesFile + const worktypesFileJson = JSON.stringify(worktypesFile); + fs.writeFileSync(`${outputFilePath}worktypes.json`, worktypesFileJson); } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); + throw new HttpException( + makeErrorResponse("E009999"), + HttpStatus.INTERNAL_SERVER_ERROR + ); } finally { this.logger.log( `[OUT] [${context.getTrackingId()}] ${this.outputJsonFile.name}` @@ -300,6 +350,8 @@ export class TransferService { ); try { + // アカウントに対するworktypeのMap配列を作成する + const accountWorktypeMap = new Map(); // csvInputFileのバリデーションチェックを行う csvInputFile.forEach((line, index) => { // typeのバリデーションチェック @@ -320,7 +372,6 @@ export class TransferService { // countryのバリデーションチェック if (line.country) { if (!COUNTRY_LIST.find((country) => country.label === line.country)) { - console.log(line.country); throw new HttpException( `country is invalid. index=${index} country=${line.country}`, HttpStatus.BAD_REQUEST @@ -329,7 +380,8 @@ export class TransferService { } // mailのバリデーションチェック // メールアドレスの形式が正しいかどうかのチェック - const mailRegExp = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; + const mailRegExp = + /^[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]+@[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]*\.[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]*[a-zA-Z]$/; if (line.email) { if (!mailRegExp.test(line.email)) { throw new HttpException( @@ -360,13 +412,181 @@ export class TransferService { ); } } + // worktypeの1アカウント20件上限チェック + for (let i = 1; i <= WORKTYPE_MAX_COUNT; i++) { + const wt = `wt${i}`; + if (line[wt]) { + if (accountWorktypeMap.has(line.account_id)) { + const worktypes = accountWorktypeMap.get(line.account_id); + // 重複している場合はPushしない + if (worktypes?.includes(line[wt])) { + continue; + } else { + worktypes?.push(line[wt]); + } + // 20件を超えたらエラー + if (worktypes?.length > WORKTYPE_MAX_COUNT) { + throw new HttpException( + `worktype is over. index=${index} account_id=${line.account_id}`, + HttpStatus.BAD_REQUEST + ); + } + } else { + accountWorktypeMap.set(line.account_id, [line[wt]]); + } + } + } }); } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); + throw new HttpException( + makeErrorResponse("E009999"), + HttpStatus.INTERNAL_SERVER_ERROR + ); } finally { this.logger.log( `[OUT] [${context.getTrackingId()}] ${this.validateInputData.name}` ); } } + + /** + * removeDuplicateEmail + * @param accountsFileLines: AccountsFile[] + * @param usersFileLines: UsersFile[] + * @param licensesFileLines: LicensesFile[] + * @returns registInputDataResponse + */ + async removeDuplicateEmail( + context: Context, + accountsFileLines: AccountsFile[], + usersFileLines: UsersFile[], + licensesFileLines: LicensesFile[] + ): Promise { + // パラメータ内容が長大なのでログには出さない + this.logger.log( + `[IN] [${context.getTrackingId()}] ${this.removeDuplicateEmail.name}` + ); + + try { + const newAccountsFileLines: AccountsFile[] = []; + const newUsersFileLines: UsersFile[] = []; + const newLicensesFileLines: LicensesFile[] = [...licensesFileLines]; // licensesFileLinesを新規配列にコピー + + // accountsFileLinesの行ループ + accountsFileLines.forEach((account) => { + const duplicateAdminMail = newAccountsFileLines.find( + (a) => a.adminMail === account.adminMail + ); + + if (duplicateAdminMail) { + // 重複がある場合はどちらが取込対象か判断できないのでファイルを出力し、エラーにする + const errorFileJson = JSON.stringify(account); + fs.writeFileSync(`duplicate_error.json`, errorFileJson); + throw new HttpException( + `adminMail is duplicate. adminMail=${account.adminMail}`, + HttpStatus.BAD_REQUEST + ); + } else { + // 重複がない場合 + newAccountsFileLines.push(account); + } + }); + + // usersFileLinesの行ループ + usersFileLines.forEach((user) => { + const duplicateUserEmail = newUsersFileLines.find( + (u) => u.email === user.email + ); + + if (duplicateUserEmail) { + // 重複がある場合 + const index = newLicensesFileLines.findIndex( + (license) => + license.account_id === user.accountId && + license.allocated_user_id === duplicateUserEmail.userId + ); + if (index !== -1) { + // ライセンスの割り当てを解除 + newLicensesFileLines[index].status = + LICENSE_ALLOCATED_STATUS.REUSABLE; + newLicensesFileLines[index].allocated_user_id = null; + } + } else { + // 重複がない場合 + newUsersFileLines.push(user); + } + // newAccountsFileLinesとの突合せ + const duplicateAdminUserEmail = newAccountsFileLines.find( + (a) => a.adminMail === user.email + ); + // 重複がある場合 + if (duplicateAdminUserEmail) { + // 同一アカウント内での重複の場合 + const isDuplicateInSameAccount = + duplicateAdminUserEmail.accountId === user.accountId; + + if (isDuplicateInSameAccount) { + // アカウント管理者にauthorロールを付与する + duplicateAdminUserEmail.role = USER_ROLES.AUTHOR; + duplicateAdminUserEmail.authorId = user.authorId; + + // アカウントにライセンスが割り当てられているか確認する + const isAllocatedLicense = newLicensesFileLines.some( + (license) => + license.account_id === duplicateAdminUserEmail.accountId && + license.allocated_user_id === duplicateAdminUserEmail.userId + ); + // 割り当てられていなければアカウントに割り当てる + if (!isAllocatedLicense) { + const index = newLicensesFileLines.findIndex( + (license) => + license.account_id === user.accountId && + license.allocated_user_id === user.userId + ); + if (index !== -1) { + newLicensesFileLines[index].allocated_user_id = + duplicateAdminUserEmail.userId; + } + } + } + // ユーザーから割り当て解除する + const index = newLicensesFileLines.findIndex( + (license) => + license.account_id === user.accountId && + license.allocated_user_id === user.userId + ); + if (index !== -1) { + // ライセンスの割り当てを解除 + newLicensesFileLines[index].status = + LICENSE_ALLOCATED_STATUS.REUSABLE; + newLicensesFileLines[index].allocated_user_id = null; + } + // ユーザーの削除 + const userIndex = newUsersFileLines.findIndex( + (u) => u.userId === user.userId + ); + if (userIndex !== -1) { + newUsersFileLines.splice(userIndex, 1); + } + } + }); + + return { + accountsFileLines: newAccountsFileLines, + usersFileLines: newUsersFileLines, + licensesFileLines: newLicensesFileLines, + }; + } catch (e) { + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + throw new HttpException( + makeErrorResponse("E009999"), + HttpStatus.INTERNAL_SERVER_ERROR + ); + } finally { + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${this.removeDuplicateEmail.name}` + ); + } + } } diff --git a/data_migration_tools/server/src/features/transfer/types/types.ts b/data_migration_tools/server/src/features/transfer/types/types.ts index 63eb3ab..d4dd969 100644 --- a/data_migration_tools/server/src/features/transfer/types/types.ts +++ b/data_migration_tools/server/src/features/transfer/types/types.ts @@ -1,9 +1,10 @@ import { ApiProperty } from "@nestjs/swagger"; import { - AccountsOutputFileStep1, - LicensesOutputFile, - UsersOutputFile, - WorktypesOutputFile, + AccountsFile, + AccountsFileType, + LicensesFile, + UsersFile, + WorktypesFile, } from "src/common/types/types"; export class transferRequest { @@ -15,11 +16,20 @@ export class transferResponse {} export class registInputDataResponse { @ApiProperty() - accountsOutputFileStep1Lines: AccountsOutputFileStep1[]; + accountsFileTypeLines: AccountsFileType[]; @ApiProperty() - usersOutputFileLines: UsersOutputFile[]; + usersFileLines: UsersFile[]; @ApiProperty() - licensesOutputFileLines: LicensesOutputFile[]; + licensesFileLines: LicensesFile[]; @ApiProperty() - worktypesOutputFileLines: WorktypesOutputFile[]; + worktypesFileLines: WorktypesFile[]; +} + +export class removeDuplicateEmailResponse { + @ApiProperty() + accountsFileLines: AccountsFile[]; + @ApiProperty() + usersFileLines: UsersFile[]; + @ApiProperty() + licensesFileLines: LicensesFile[]; } 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 b38f6ba..96d5030 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 @@ -28,6 +28,7 @@ export class AccountsRepositoryService { * @param tier * @param adminExternalUserId * @param adminUserRole + * @param adminUserAuthId * @param accountId * @param userId * @param adminUserAcceptedEulaVersion @@ -43,6 +44,7 @@ export class AccountsRepositoryService { tier: number, adminExternalUserId: string, adminUserRole: string, + adminUserAuthId: string, accountId: number, userId: number, adminUserAcceptedEulaVersion?: string, @@ -75,6 +77,7 @@ export class AccountsRepositoryService { user.account_id = persistedAccount.id; user.external_id = adminExternalUserId; user.role = adminUserRole; + user.author_id = adminUserAuthId; user.accepted_eula_version = adminUserAcceptedEulaVersion ?? null; user.accepted_privacy_notice_version = adminUserAcceptedPrivacyNoticeVersion ?? null; 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 643b7f4..df1c190 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 @@ -1,5 +1,5 @@ import { Injectable, Logger } from "@nestjs/common"; -import { DataSource, In } from "typeorm"; +import { DataSource } from "typeorm"; import { License, LicenseAllocationHistory, @@ -8,15 +8,10 @@ import { } from "./entity/license.entity"; 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"; + +import { AUTO_INCREMENT_START } from "../../constants/index"; +import { LICENSE_ALLOCATED_STATUS, LICENSE_TYPE } from "../../constants"; +import { CardLicensesFile, LicensesFile } from "src/common/types/types"; @Injectable() export class LicensesRepositoryService { //クエリログにコメントを出力するかどうか @@ -27,27 +22,27 @@ export class LicensesRepositoryService { /** * ライセンスを登録する * @context Context - * @param licensesInputFiles + * @param LicensesFiles */ async insertLicenses( context: Context, - licensesInputFiles: LicensesInputFile[] + LicensesFiles: LicensesFile[] ): Promise<{}> { const nowDate = new Date(); return await this.dataSource.transaction(async (entityManager) => { const licenseRepo = entityManager.getRepository(License); let newLicenses: License[] = []; - licensesInputFiles.forEach((licensesInputFile) => { + LicensesFiles.forEach((LicensesFile) => { const license = new License(); - 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) + license.account_id = LicensesFile.account_id; + license.status = LicensesFile.status; + license.type = LicensesFile.type; + license.expiry_date = LicensesFile.expiry_date + ? new Date(LicensesFile.expiry_date) : null; - if (licensesInputFile.allocated_user_id) { - license.allocated_user_id = licensesInputFile.allocated_user_id; + if (LicensesFile.allocated_user_id) { + license.allocated_user_id = LicensesFile.allocated_user_id; } newLicenses.push(license); }); @@ -96,11 +91,11 @@ export class LicensesRepositoryService { /** * カードライセンスを登録する * @context Context - * @param cardLicensesInputFiles + * @param cardLicensesFiles */ async insertCardLicenses( context: Context, - cardLicensesInputFiles: CardLicensesInputFile[] + cardLicensesFiles: CardLicensesFile[] ): Promise<{}> { return await this.dataSource.transaction(async (entityManager) => { const cardLicenseRepo = entityManager.getRepository(CardLicense); @@ -110,7 +105,7 @@ export class LicensesRepositoryService { const licenses: License[] = []; // ライセンステーブルを作成する(BULK INSERT) - for (let i = 0; i < cardLicensesInputFiles.length; i++) { + for (let i = 0; i < cardLicensesFiles.length; i++) { const license = new License(); license.account_id = AUTO_INCREMENT_START; // 最初に登場するアカウント(第一アカウント) license.status = LICENSE_ALLOCATED_STATUS.UNALLOCATED; @@ -139,23 +134,22 @@ export class LicensesRepositoryService { const newCardLicenses: CardLicense[] = []; // カードライセンステーブルを作成する(BULK INSERT) - for (let i = 0; i < cardLicensesInputFiles.length; i++) { + for (let i = 0; i < cardLicensesFiles.length; i++) { const cardLicense = new CardLicense(); 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) + cardLicense.card_license_key = cardLicensesFiles[i].card_license_key; + cardLicense.activated_at = cardLicensesFiles[i].activated_at + ? new Date(cardLicensesFiles[i].activated_at) : null; - cardLicense.created_at = cardLicensesInputFiles[i].created_at - ? new Date(cardLicensesInputFiles[i].created_at) + cardLicense.created_at = cardLicensesFiles[i].created_at + ? new Date(cardLicensesFiles[i].created_at) : null; - cardLicense.created_by = cardLicensesInputFiles[i].created_by; - cardLicense.updated_at = cardLicensesInputFiles[i].updated_at - ? new Date(cardLicensesInputFiles[i].updated_at) + cardLicense.created_by = cardLicensesFiles[i].created_by; + cardLicense.updated_at = cardLicensesFiles[i].updated_at + ? new Date(cardLicensesFiles[i].updated_at) : null; - cardLicense.updated_by = cardLicensesInputFiles[i].updated_by; + cardLicense.updated_by = cardLicensesFiles[i].updated_by; newCardLicenses.push(cardLicense); } diff --git a/data_migration_tools/server/src/repositories/worktypes/worktypes.repository.service.ts b/data_migration_tools/server/src/repositories/worktypes/worktypes.repository.service.ts index 9f7f2ff..3245b63 100644 --- a/data_migration_tools/server/src/repositories/worktypes/worktypes.repository.service.ts +++ b/data_migration_tools/server/src/repositories/worktypes/worktypes.repository.service.ts @@ -13,7 +13,7 @@ import { import { OptionItem } from "./entity/option_item.entity"; import { insertEntities, insertEntity } from "../../common/repository"; import { Context } from "../../common/log"; -import { WorktypesInputFile } from "../../common/types/types"; +import { WorktypesFile } from "../../common/types/types"; @Injectable() export class WorktypesRepositoryService { @@ -30,15 +30,15 @@ export class WorktypesRepositoryService { */ async createWorktype( context: Context, - worktypesInputFiles: WorktypesInputFile[] + WorktypesFiles: WorktypesFile[] ): Promise { await this.dataSource.transaction(async (entityManager) => { const worktypeRepo = entityManager.getRepository(Worktype); const optionItemRepo = entityManager.getRepository(OptionItem); - for (const worktypesInputFile of worktypesInputFiles) { - const accountId = worktypesInputFile.account_id; - const worktypeId = worktypesInputFile.custom_worktype_id; + for (const WorktypesFile of WorktypesFiles) { + const accountId = WorktypesFile.account_id; + const worktypeId = WorktypesFile.custom_worktype_id; const description = null; const duplicatedWorktype = await worktypeRepo.findOne({