From f6d39a4c26d7fb0d32a0b43cf32ed28e4270f05c Mon Sep 17 00:00:00 2001 From: "maruyama.t" Date: Tue, 27 Feb 2024 23:55:44 +0000 Subject: [PATCH 1/4] =?UTF-8?q?Merged=20PR=20788:=20[2=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?=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 ## 概要 [Task3577: [2回目実行]実施後の動作確認](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3577) accountがCountryの場合に配下のDistributorの親アカウントIDを付け替える処理について、Typeの付け替えができていなかったのを修正。 ライセンスの有効期限が"9999/12/31 23:59:59.997"でフォーマットチェックをしているが、移行元は"9999/12/31 23:59:59"なので移行元に合わせた。 dealerAccountIdが設定されているが、そのdealerが存在しない場合もデータを作ってしまっている。 →該当レコードはエラーファイルを出力する。 ## レビューポイント - エラーファイルの出力処理だが簡素すぎるか? JSONで出力する意味はないが、これまでの動作確認で動作担保できているのでJSONで出しています。 ## 動作確認状況 - ローカルで確認 正常の場合データ変換が行われることを確認。 dealerAccountIdが設定されているが、そのdealerが存在しない場合もデータでテストした場合、error.jsonが作られることを確認。 ## 補足 - 相談、参考資料などがあれば --- .../src/features/transfer/transfer.service.ts | 46 +++++++++++-------- 1 file changed, 28 insertions(+), 18 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 c1b0757..cf8dd53 100644 --- a/data_migration_tools/server/src/features/transfer/transfer.service.ts +++ b/data_migration_tools/server/src/features/transfer/transfer.service.ts @@ -50,7 +50,7 @@ export class TransferService { let usersFileLines: UsersFile[] = []; let licensesFileLines: LicensesFile[] = []; let worktypesFileLines: WorktypesFile[] = []; - + let errorArray: string[] = []; let userIdIndex = 0; // authorIdとuserIdの対応関係を保持するMapを定義 const authorIdToUserIdMap: Map = new Map(); @@ -75,9 +75,11 @@ export class TransferService { if (line.parent_id) { parentAccountId = accountIdMap.get(line.parent_id); } - // 万が一parent_idが入力されているのに存在しなかった場合は、nullを設定する。 + // 万が一parent_idが入力されているのに存在しなかった場合は、エラー配列に追加する if (parentAccountId === undefined) { - parentAccountId = null; + errorArray.push( + `parent_id is invalid. parent_id=${line.parent_id}` + ); } // userIdIndexをインクリメントする @@ -126,8 +128,8 @@ export class TransferService { } // ライセンスのデータの作成を行う - // line.expired_dateが9999/12/31 23:59:59.997のデータの場合はデモライセンスなので登録しない - if (line.expired_date !== "9999/12/31 23:59:59.997") { + // line.expired_dateが"9999/12/31 23:59:59"のデータの場合はデモライセンスなので登録しない + if (line.expired_date !== "9999/12/31 23:59:59") { // authorIdが設定されてる場合、statusは"allocated"、allocated_user_idは対象のユーザID // されていない場合、statusは"reusable"、allocated_user_idはnull let status: string; @@ -176,6 +178,15 @@ export class TransferService { } } }); + // エラー配列に値が存在する場合はエラーファイルを出力する + if (errorArray.length > 0) { + const errorFileJson = JSON.stringify(errorArray); + fs.writeFileSync(`error.json`, errorFileJson); + throw new HttpException( + `errorArray is invalid. errorArray=${errorArray}`, + HttpStatus.BAD_REQUEST + ); + } return { accountsFileTypeLines, usersFileLines, @@ -211,24 +222,23 @@ export class TransferService { try { const relocatedAccounts: AccountsFile[] = []; - const countryRecords: Map = new Map(); + const dealerRecords: Map = new Map(); // 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 + // Distributorの場合はdealerを検索し、COUNTRYかチェックする + if (account.type === MIGRATION_TYPE.DISTRIBUTOR) { + const distributorParent = accountsFileType.find( + (a) => a.accountId === account.dealerAccountId ); - if (distributor) { - countryRecords.set(distributor.accountId, account.dealerAccountId); + if (distributorParent.type === MIGRATION_TYPE.COUNTRY) { + dealerRecords.set( + account.accountId, + distributorParent.dealerAccountId // Countryの親、BCのIDを設定 + ); } } else { - // Country以外のアカウントの場合は、そのまま登録 - countryRecords.set(account.accountId, account.dealerAccountId); + dealerRecords.set(account.accountId, account.dealerAccountId); } }); @@ -237,7 +247,7 @@ export class TransferService { // Countryのレコードは除外する if (account.type !== MIGRATION_TYPE.COUNTRY) { const dealerAccountId = - countryRecords.get(account.dealerAccountId) ?? + dealerRecords.get(account.dealerAccountId) ?? account.dealerAccountId; const type = this.getAccountType(account.type); const newAccount: AccountsFile = { From 0be9c26f09ff57a380e41b2b1c7bbf3e00fbcc5a Mon Sep 17 00:00:00 2001 From: masaaki Date: Wed, 28 Feb 2024 05:31:13 +0000 Subject: [PATCH 2/4] =?UTF-8?q?Merged=20PR=20781:=20=E3=83=87=E3=83=BC?= =?UTF-8?q?=E3=82=BF=E6=A4=9C=E8=A8=BC=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 ## 概要 [Task3573: データ検証ツール作成+動作確認](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3573) - データ検証ツールを作成しました ## レビューポイント - 特にレビューしてほしい箇所  詳細情報の突き合わせについて、ラフスケッチと対応しているか第三者目線でも確認してほしいです  verification.serviceのcompareCardLicenses、compareLicenses、compareAccountsになります。 ## UIの変更 - 無し ## 動作確認状況 - ローカルで確認 ## 補足 - 無し --- data_migration_tools/server/package-lock.json | 66 ++ data_migration_tools/server/package.json | 3 +- data_migration_tools/server/src/app.module.ts | 7 + .../server/src/common/types/types.ts | 83 +++ .../features/transfer/transfer.controller.ts | 17 +- .../src/features/verification/types/types.ts | 9 + .../verification/verification.controller.ts | 148 ++++ .../verification/verification.module.ts | 17 + .../verification/verification.service.ts | 695 ++++++++++++++++++ .../accounts/accounts.repository.service.ts | 17 + .../licenses/licenses.repository.service.ts | 30 + .../users/users.repository.service.ts | 16 + 12 files changed, 1106 insertions(+), 2 deletions(-) create mode 100644 data_migration_tools/server/src/features/verification/types/types.ts create mode 100644 data_migration_tools/server/src/features/verification/verification.controller.ts create mode 100644 data_migration_tools/server/src/features/verification/verification.module.ts create mode 100644 data_migration_tools/server/src/features/verification/verification.service.ts diff --git a/data_migration_tools/server/package-lock.json b/data_migration_tools/server/package-lock.json index c1c2143..1a8aff6 100644 --- a/data_migration_tools/server/package-lock.json +++ b/data_migration_tools/server/package-lock.json @@ -26,6 +26,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "cookie-parser": "^1.4.6", + "csv": "^6.3.6", "multer": "^1.4.5-lts.1", "mysql2": "^2.3.3", "reflect-metadata": "^0.1.13", @@ -4049,6 +4050,35 @@ "node": ">= 8" } }, + "node_modules/csv": { + "version": "6.3.6", + "resolved": "https://registry.npmjs.org/csv/-/csv-6.3.6.tgz", + "integrity": "sha512-jsEsX2HhGp7xiwrJu5srQavKsh+HUJcCi78Ar3m4jlmFKRoTkkMy7ZZPP+LnQChmaztW+uj44oyfMb59daAs/Q==", + "dependencies": { + "csv-generate": "^4.3.1", + "csv-parse": "^5.5.3", + "csv-stringify": "^6.4.5", + "stream-transform": "^3.3.0" + }, + "engines": { + "node": ">= 0.1.90" + } + }, + "node_modules/csv-generate": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/csv-generate/-/csv-generate-4.3.1.tgz", + "integrity": "sha512-7YeeJq+44/I/O5N2sr2qBMcHZXhpfe38eh7DOFxyMtYO+Pir7kIfgFkW5MPksqKqqR6+/wX7UGoZm1Ot11151w==" + }, + "node_modules/csv-parse": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.5.3.tgz", + "integrity": "sha512-v0KW6C0qlZzoGjk6u5tLmVfyZxNgPGXZsWTXshpAgKVGmGXzaVWGdlCFxNx5iuzcXT/oJN1HHM9DZKwtAtYa+A==" + }, + "node_modules/csv-stringify": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.4.5.tgz", + "integrity": "sha512-SPu1Vnh8U5EnzpNOi1NDBL5jU5Rx7DVHr15DNg9LXDTAbQlAVAmEbVt16wZvEW9Fu9Qt4Ji8kmeCJ2B1+4rFTQ==" + }, "node_modules/date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", @@ -8654,6 +8684,11 @@ "npm": ">=6" } }, + "node_modules/stream-transform": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-3.3.0.tgz", + "integrity": "sha512-pG1NeDdmErNYKtvTpFayrEueAmL0xVU5wd22V7InGnatl4Ocq3HY7dcXIKj629kXvYQvglCC7CeDIGAlx1RNGA==" + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -13027,6 +13062,32 @@ "which": "^2.0.1" } }, + "csv": { + "version": "6.3.6", + "resolved": "https://registry.npmjs.org/csv/-/csv-6.3.6.tgz", + "integrity": "sha512-jsEsX2HhGp7xiwrJu5srQavKsh+HUJcCi78Ar3m4jlmFKRoTkkMy7ZZPP+LnQChmaztW+uj44oyfMb59daAs/Q==", + "requires": { + "csv-generate": "^4.3.1", + "csv-parse": "^5.5.3", + "csv-stringify": "^6.4.5", + "stream-transform": "^3.3.0" + } + }, + "csv-generate": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/csv-generate/-/csv-generate-4.3.1.tgz", + "integrity": "sha512-7YeeJq+44/I/O5N2sr2qBMcHZXhpfe38eh7DOFxyMtYO+Pir7kIfgFkW5MPksqKqqR6+/wX7UGoZm1Ot11151w==" + }, + "csv-parse": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.5.3.tgz", + "integrity": "sha512-v0KW6C0qlZzoGjk6u5tLmVfyZxNgPGXZsWTXshpAgKVGmGXzaVWGdlCFxNx5iuzcXT/oJN1HHM9DZKwtAtYa+A==" + }, + "csv-stringify": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.4.5.tgz", + "integrity": "sha512-SPu1Vnh8U5EnzpNOi1NDBL5jU5Rx7DVHr15DNg9LXDTAbQlAVAmEbVt16wZvEW9Fu9Qt4Ji8kmeCJ2B1+4rFTQ==" + }, "date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", @@ -16557,6 +16618,11 @@ "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==" }, + "stream-transform": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-3.3.0.tgz", + "integrity": "sha512-pG1NeDdmErNYKtvTpFayrEueAmL0xVU5wd22V7InGnatl4Ocq3HY7dcXIKj629kXvYQvglCC7CeDIGAlx1RNGA==" + }, "streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", diff --git a/data_migration_tools/server/package.json b/data_migration_tools/server/package.json index a2299cb..a0502b7 100644 --- a/data_migration_tools/server/package.json +++ b/data_migration_tools/server/package.json @@ -45,7 +45,8 @@ "reflect-metadata": "^0.1.13", "rxjs": "^7.8.0", "swagger-cli": "^4.0.4", - "typeorm": "^0.3.20" + "typeorm": "^0.3.20", + "csv": "^6.3.6" }, "devDependencies": { "@types/express": "^4.17.17", diff --git a/data_migration_tools/server/src/app.module.ts b/data_migration_tools/server/src/app.module.ts index 6935bba..bcf32fe 100644 --- a/data_migration_tools/server/src/app.module.ts +++ b/data_migration_tools/server/src/app.module.ts @@ -27,6 +27,10 @@ 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"; +import { VerificationController } from "./features/verification/verification.controller"; +import { VerificationService } from "./features/verification/verification.service"; +import { VerificationModule } from "./features/verification/verification.module"; + @Module({ imports: [ ServeStaticModule.forRoot({ @@ -41,6 +45,7 @@ import { TransferService } from "./features/transfer/transfer.service"; UsersModule, TransferModule, RegisterModule, + VerificationModule, AccountsRepositoryModule, UsersRepositoryModule, SortCriteriaRepositoryModule, @@ -70,6 +75,7 @@ import { TransferService } from "./features/transfer/transfer.service"; UsersController, DeleteController, TransferController, + VerificationController, ], providers: [ RegisterService, @@ -77,6 +83,7 @@ import { TransferService } from "./features/transfer/transfer.service"; UsersService, DeleteService, TransferService, + VerificationService, ], }) export class AppModule { diff --git a/data_migration_tools/server/src/common/types/types.ts b/data_migration_tools/server/src/common/types/types.ts index 69ef466..529a4ad 100644 --- a/data_migration_tools/server/src/common/types/types.ts +++ b/data_migration_tools/server/src/common/types/types.ts @@ -34,6 +34,11 @@ export class csvInputFile { wt19: string; wt20: string; } + +export class csvInputFileWithRow extends csvInputFile { + row: number; +} + export class AccountsFileType { accountId: number; type: string; @@ -93,6 +98,22 @@ export class CardLicensesFile { updated_by?: string; } +export class AccountsMappingFile { + accountIdText: string; + accountIdNumber: number; +} + +export class VerificationResultDetails { + input: string; + inputRow: number; + diffTargetTable: string; + columnName: string; + fileData: string; + databaseData: string; + reason: string; +} + + export function isAccountsFileArray(obj: any): obj is AccountsFile[] { return Array.isArray(obj) && obj.every((item) => isAccountsFile(item)); } @@ -202,3 +223,65 @@ export function isCardLicensesFile(obj: any): obj is CardLicensesFile { (obj.updated_by === null || typeof obj.updated_by === "string") ); } + +export function isAccountsMappingFileArray( + obj: any +): obj is AccountsMappingFile[] { + return Array.isArray(obj) && obj.every((item) => isAccountsMappingFile(item)); +} +export function isAccountsMappingFile(obj: any): obj is AccountsMappingFile { + return ( + typeof obj === "object" && + obj !== null && + "accountIdText" in obj && + "accountIdNumber" in obj && + typeof obj.accountIdText === "string" && + typeof obj.accountIdNumber === "number" + ); +} + +export function isCsvInputFileForValidateArray(obj: any): obj is csvInputFile[] { + return ( + Array.isArray(obj) && obj.every((item) => isCsvInputFileForValidate(item)) + ); +} + +export function isCsvInputFileForValidate(obj: any): obj is csvInputFile { + return ( + typeof obj === "object" && + "type" in obj && + "account_id" in obj && + "parent_id" in obj && + "email" in obj && + "company_name" in obj && + "first_name" in obj && + "last_name" in obj && + "country" in obj && + "state" in obj && + "start_date" in obj && + "expired_date" in obj && + "user_email" in obj && + "author_id" in obj && + "recording_mode" in obj && + "wt1" in obj && + "wt2" in obj && + "wt3" in obj && + "wt4" in obj && + "wt5" in obj && + "wt6" in obj && + "wt7" in obj && + "wt8" in obj && + "wt9" in obj && + "wt10" in obj && + "wt11" in obj && + "wt12" in obj && + "wt13" in obj && + "wt14" in obj && + "wt15" in obj && + "wt16" in obj && + "wt17" in obj && + "wt18" in obj && + "wt19" in obj && + "wt20" in obj + ); +} 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 7916f27..ee38969 100644 --- a/data_migration_tools/server/src/features/transfer/transfer.controller.ts +++ b/data_migration_tools/server/src/features/transfer/transfer.controller.ts @@ -13,7 +13,7 @@ import { Request } from "express"; import { transferRequest, transferResponse } from "./types/types"; import { TransferService } from "./transfer.service"; import { makeContext } from "../../common/log"; -import { csvInputFile } from "../../common/types/types"; +import { csvInputFile, AccountsMappingFile } from "../../common/types/types"; import { makeErrorResponse } from "src/common/errors/makeErrorResponse"; import { AUTO_INCREMENT_START } from "../../constants"; @ApiTags("transfer") @@ -149,6 +149,21 @@ export class TransferController { accountIdListArray.forEach((accountId, index) => { accountIdMap.set(accountId, index + AUTO_INCREMENT_START); }); + + // アカウントID numberとstring対応表の出力 + const accountsMappingFiles: AccountsMappingFile[] = []; + accountIdMap.forEach((value, key) => { + const accountsMappingFile = new AccountsMappingFile(); + accountsMappingFile.accountIdNumber = value; + accountsMappingFile.accountIdText = key + accountsMappingFiles.push(accountsMappingFile) + }); + + fs.writeFileSync( + `${inputFilePath}account_map.json`, + JSON.stringify(accountsMappingFiles) + ); + // CSVファイルの変換 const transferResponseCsv = await this.transferService.transferInputData( context, diff --git a/data_migration_tools/server/src/features/verification/types/types.ts b/data_migration_tools/server/src/features/verification/types/types.ts new file mode 100644 index 0000000..2eda873 --- /dev/null +++ b/data_migration_tools/server/src/features/verification/types/types.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class VerificationRequest { + @ApiProperty() + inputFilePath: string; +} + +export class VerificationResponse {} + diff --git a/data_migration_tools/server/src/features/verification/verification.controller.ts b/data_migration_tools/server/src/features/verification/verification.controller.ts new file mode 100644 index 0000000..31cde89 --- /dev/null +++ b/data_migration_tools/server/src/features/verification/verification.controller.ts @@ -0,0 +1,148 @@ +import { + Body, + Controller, + HttpStatus, + Post, + Req, + Logger, + HttpException, +} from "@nestjs/common"; +import { makeErrorResponse } from "../../common/error/makeErrorResponse"; +import fs from "fs"; +import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; +import { Request } from "express"; +import { VerificationRequest, VerificationResponse } from "./types/types"; +import { VerificationService } from "./verification.service"; +import { makeContext } from "../../common/log"; +import { + csvInputFileWithRow, + isAccountsMappingFileArray, + isCardLicensesFileArray, + isCsvInputFileForValidateArray, +} from "../../common/types/types"; +import * as csv from "csv"; + +@ApiTags("verification") +@Controller("verification") +export class VerificationController { + private readonly logger = new Logger(VerificationController.name); + constructor(private readonly verificationService: VerificationService) {} + + @Post() + @ApiResponse({ + status: HttpStatus.OK, + type: VerificationResponse, + description: "成功時のレスポンス", + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: "想定外のサーバーエラー", + }) + @ApiOperation({ operationId: "dataVerification" }) + async dataVerification( + @Body() body: VerificationRequest, + @Req() req: Request + ): Promise { + const context = makeContext("iko", "varification"); + + const inputFilePath = body.inputFilePath; + + this.logger.log( + `[IN] [${context.getTrackingId()}] ${ + this.dataVerification.name + } | params: { inputFilePath: ${inputFilePath}};` + ); + + try { + // 読み込みファイルのフルパス + const accountTransitionFileFullPath = + inputFilePath + "Account_transition.csv"; + const accountMapFileFullPath = inputFilePath + "account_map.json"; + const cardLicensesFileFullPath = inputFilePath + "cardLicenses.json"; + + // ファイル存在チェックと読み込み + if (!fs.existsSync(accountTransitionFileFullPath)) { + this.logger.error( + `file not exists from ${accountTransitionFileFullPath}` + ); + throw new Error( + `file not exists from ${accountTransitionFileFullPath}` + ); + } + + if (!fs.existsSync(accountMapFileFullPath)) { + this.logger.error(`file not exists from ${accountMapFileFullPath}`); + throw new Error(`file not exists from ${accountMapFileFullPath}`); + } + + if (!fs.existsSync(cardLicensesFileFullPath)) { + this.logger.error(`file not exists from ${cardLicensesFileFullPath}`); + throw new Error(`file not exists from ${cardLicensesFileFullPath}`); + } + + // カードライセンスの登録用ファイル読み込み + const cardLicensesObject = JSON.parse( + fs.readFileSync(cardLicensesFileFullPath, "utf8") + ); + + // 型ガード(cardLicenses) + if (!isCardLicensesFileArray(cardLicensesObject)) { + throw new Error("input file is not cardLicensesInputFiles"); + } + + // アカウントIDマッピング用ファイル読み込み + const accountsMapObject = JSON.parse( + fs.readFileSync(accountMapFileFullPath, "utf8") + ); + + // 型ガード(accountsMapingFile) + if (!isAccountsMappingFileArray(accountsMapObject)) { + throw new Error("input file is not accountsMapingFile"); + } + + // 移行用csvファイルの読み込み(csv parse) + fs.createReadStream(accountTransitionFileFullPath).pipe( + csv.parse({ columns: true, delimiter: "," }, (err, csvInputFiles) => { + // 型ガード(csvInputFile) + if (!isCsvInputFileForValidateArray(csvInputFiles)) { + throw new Error("input file is not csvInputFile"); + } + + const csvInputFileswithRows: csvInputFileWithRow[] = []; + let rowCount = 2; // csvの何行目かを表す変数。ヘッダ行があるので2から開始 + for (const csvInputFile of csvInputFiles) { + const csvInputFileswithRow: csvInputFileWithRow = { + ...csvInputFile, + row: rowCount + }; + csvInputFileswithRows.push(csvInputFileswithRow); + rowCount = rowCount + 1; + } + this.verificationService.varificationData( + context, + inputFilePath, + csvInputFileswithRows, + accountsMapObject, + cardLicensesObject + ); + }) + ); + + return {}; + } 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.dataVerification.name}` + ); + } + } +} + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/data_migration_tools/server/src/features/verification/verification.module.ts b/data_migration_tools/server/src/features/verification/verification.module.ts new file mode 100644 index 0000000..51b9892 --- /dev/null +++ b/data_migration_tools/server/src/features/verification/verification.module.ts @@ -0,0 +1,17 @@ +import { Module } from "@nestjs/common"; +import { VerificationController } from "./verification.controller"; +import { VerificationService } from "./verification.service"; +import { LicensesRepositoryModule } from "../../repositories/licenses/licenses.repository.module"; +import { AccountsRepositoryModule } from "../../repositories/accounts/accounts.repository.module"; +import { UsersRepositoryModule } from "../../repositories//users/users.repository.module"; + +@Module({ + imports: [ + LicensesRepositoryModule, + AccountsRepositoryModule, + UsersRepositoryModule, + ], + controllers: [VerificationController], + providers: [VerificationService], +}) +export class VerificationModule {} diff --git a/data_migration_tools/server/src/features/verification/verification.service.ts b/data_migration_tools/server/src/features/verification/verification.service.ts new file mode 100644 index 0000000..1de9596 --- /dev/null +++ b/data_migration_tools/server/src/features/verification/verification.service.ts @@ -0,0 +1,695 @@ +import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common"; +import { Context } from "../../common/log"; +import { + AccountsMappingFile, + CardLicensesFile, + csvInputFileWithRow, + VerificationResultDetails, +} from "../../common/types/types"; +import { + AUTO_INCREMENT_START, + MIGRATION_TYPE, + COUNTRY_LIST, +} from "../../constants/index"; + +import { makeErrorResponse } from "../../common/error/makeErrorResponse"; +import { LicensesRepositoryService } from "../../repositories/licenses/licenses.repository.service"; +import { + License, + CardLicense, +} from "../../repositories/licenses/entity/license.entity"; +import { AccountsRepositoryService } from "../../repositories/accounts/accounts.repository.service"; +import { UsersRepositoryService } from "../../repositories//users/users.repository.service"; +import { Account } from "src/repositories/accounts/entity/account.entity"; +import fs from "fs"; + +@Injectable() +export class VerificationService { + constructor( + private readonly AccountsRepository: AccountsRepositoryService, + private readonly UsersRepository: UsersRepositoryService, + private readonly licensesRepository: LicensesRepositoryService + ) {} + private readonly logger = new Logger(VerificationService.name); + + /** + * Verification Data + * @param inputFilePath: string + */ + async varificationData( + context: Context, + inputFilePath: string, + csvInputFiles: csvInputFileWithRow[], + accountsMappingInputFiles: AccountsMappingFile[], + cardlicensesInputFiles: CardLicensesFile[] + ): Promise { + // パラメータ内容が長大なのでログには出さない + this.logger.log( + `[IN] [${context.getTrackingId()}] ${this.varificationData.name}` + ); + + // this.logger.log(csvInputFiles); + try { + // 件数情報の取得 + this.logger.log(`入力ファイルから件数情報を取得する`); + + const accountCountFromFile = csvInputFiles.filter( + (item) => item.type !== "USER" && item.type !== "Country" + ).length; + const cardLicensesCountFromFile = cardlicensesInputFiles.length; + + const licensesCountFromFile = + csvInputFiles.filter( + (item) => + item.type === "USER" && item.expired_date !== "9999/12/31 23:59:59" + ).length + cardLicensesCountFromFile; + + // 管理ユーザ数のカウント + const administratorCountFromFile = accountCountFromFile; + // 一般ユーザ数のカウント + const normaluserCountFromFile = csvInputFiles.filter( + (item) => item.type === "USER" && item.user_email.length !== 0 + ).length; + + // ユーザ重複数のカウント + let mailAdresses: string[] = []; + csvInputFiles.forEach((item) => { + // メールアドレスの要素を配列に追加(入力データとして管理者とユーザの両方に入ることはない) + if (item.email.length !== 0) { + mailAdresses.push(item.email); + } + if (item.user_email.length !== 0) { + mailAdresses.push(item.user_email); + } + }); + + // 重複する要素を抽出 + const duplicates: { [key: string]: number } = {}; + mailAdresses.forEach((str) => { + duplicates[str] = (duplicates[str] || 0) + 1; + }); + + // 重複する要素と件数を表示 + let duplicateCount = 0; + Object.keys(duplicates).forEach((key) => { + const count = duplicates[key]; + if (count > 1) { + // 重複件数をカウント + duplicateCount = duplicateCount + (count - 1); + //console.log(`${key}が${count}件`); + } + }); + const userCountFromFile = + administratorCountFromFile + normaluserCountFromFile - duplicateCount; + + this.logger.log(`accountCountFromFile=${accountCountFromFile}`); + this.logger.log(`cardLicensesCountFromFile=${cardLicensesCountFromFile}`); + this.logger.log(`licensesCountFromFile=${licensesCountFromFile}`); + this.logger.log(`userCountFromFile=${userCountFromFile}`); + + // DBから情報を取得する + this.logger.log(`DBの情報を取得する`); + + const accounts = await this.AccountsRepository.getAllAccounts(context); + const users = await this.UsersRepository.getAllUsers(context); + const licenses = await this.licensesRepository.getAllLicenses(context); + const cardLicenses = await this.licensesRepository.getAllCardLicense( + context + ); + + // DB件数のカウント + this.logger.log(`DBの情報から件数を取得する`); + const accountsCountFromDB = accounts.length; + const usersCountFromDB = users.length; + const licensesCountFromDB = licenses.length; + const cardLicensesCountFromDB = cardLicenses.length; + + this.logger.log(`accountsCountFromDB=${accountsCountFromDB}`); + this.logger.log(`usersCountFromDB=${usersCountFromDB}`); + this.logger.log(`licensesCountFromDB=${licensesCountFromDB}`); + this.logger.log(`cardLicensesCountFromDB=${cardLicensesCountFromDB}`); + + // エラー情報の定義 + const VerificationResultDetails: VerificationResultDetails[] = []; + + // カードライセンス関連の情報突き合わせ + this.logger.log(`カードライセンス関連の情報突き合わせ`); + const isCardDetailNoError = compareCardLicenses( + VerificationResultDetails, + cardlicensesInputFiles, + cardLicenses, + licenses + ); + + // ライセンス関連の情報突き合わせ + this.logger.log(`ライセンス関連の情報突き合わせ`); + const isLicensesDetailNoError = compareLicenses( + VerificationResultDetails, + csvInputFiles.filter( + (item) => + item.type === "USER" && item.expired_date !== "9999/12/31 23:59:59" + ), + licenses.filter((item) => item.expiry_date !== null), + accountsMappingInputFiles + ); + + // アカウント情報の突き合わせ + this.logger.log(`アカウント関連の情報突き合わせ`); + const isAccountsDetailNoError = compareAccounts( + VerificationResultDetails, + csvInputFiles.filter( + (item) => item.type !== "USER" && item.type !== "Country" + ), + csvInputFiles.filter((item) => item.type === "Country"), + accounts, + accountsMappingInputFiles + ); + + // 結果の判定と出力 + this.logger.log(`結果の判定と出力`); + const isAccountCountNoDifference = + accountCountFromFile === accountsCountFromDB; + const isUsersCountNoDifference = userCountFromFile === usersCountFromDB; + const isLicensesCountNoDifference = + licensesCountFromFile === licensesCountFromDB; + const isCardLicensesCountNoDifference = + cardLicensesCountFromFile === cardLicensesCountFromDB; + const isNoDetailError = VerificationResultDetails.length === 0; + + const isSummaryNoError = + isAccountCountNoDifference && + isUsersCountNoDifference && + isLicensesCountNoDifference && + isCardLicensesCountNoDifference && + isNoDetailError; + + const summaryString = ` +サマリファイル: + +比較結果:${isSummaryNoError ? "OK" : "NG"} + +件数: +アカウント:${ + isAccountCountNoDifference ? "OK" : "NG" + }(csv件数:${accountCountFromFile}/DB件数:${accountsCountFromDB}) +ライセンス:${ + isLicensesCountNoDifference ? "OK" : "NG" + }(csv件数:${licensesCountFromFile}/DB件数:${licensesCountFromDB}) +カードライセンス:${ + isCardLicensesCountNoDifference ? "OK" : "NG" + }(csv件数:${cardLicensesCountFromFile}/DB件数:${cardLicensesCountFromDB}) +ユーザー:${ + isUsersCountNoDifference ? "OK" : "NG" + }(csv件数:${userCountFromFile}/DB件数:${usersCountFromDB}) + +項目比較: +アカウント:${isAccountsDetailNoError ? "OK" : "NG"} +カードライセンス:${isCardDetailNoError ? "OK" : "NG"} +ライセンス:${isLicensesDetailNoError ? "OK" : "NG"} +`; + + // サマリファイルの書き込み + fs.writeFileSync(`${inputFilePath}resultsummary.txt`, summaryString); + + // 詳細ファイルの書き込み + // 配列をJSON文字列に変換 + const jsonContent = JSON.stringify(VerificationResultDetails, null, 2); + + // JSONをファイルに書き込み + fs.writeFileSync(`${inputFilePath}resultdetail.json`, jsonContent); + } 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.varificationData.name}` + ); + } + } +} + +// dateを任意のフォーマットに変換する +const getFormattedDate = (date: Date | null, format: string) => { + if (!date) { + return null; + } + const symbol = { + M: date.getMonth() + 1, + d: date.getDate(), + h: date.getHours(), + m: date.getMinutes(), + s: date.getSeconds(), + }; + + const formatted = format.replace(/(M+|d+|h+|m+|s+)/g, (v) => + ( + (v.length > 1 ? "0" : "") + symbol[v.slice(-1) as keyof typeof symbol] + ).slice(-2) + ); + + return formatted.replace(/(y+)/g, (v) => + date.getFullYear().toString().slice(-v.length) + ); +}; + +// 親の階層がcountryの場合、countryの親を返却する +function transrateCountryHierarchy( + countriesFromFile: csvInputFileWithRow[], + targetParentAccountIdString: string +): string { + for (const countryFromFile of countriesFromFile) { + if (countryFromFile.account_id === targetParentAccountIdString) { + return countryFromFile.parent_id; + } + } + return targetParentAccountIdString; +} + +// アカウントID(number)を対応するアカウントID(string)に変換する +function findAccountIdText( + accountsMappings: AccountsMappingFile[], + targetAccountIdNumber: number +): string { + if (targetAccountIdNumber == null) { + return ""; + } + for (const accountsMapping of accountsMappings) { + if (accountsMapping.accountIdNumber === targetAccountIdNumber) { + return accountsMapping.accountIdText; + } + } + return `NO_MATCHED_ACCOUNTID_${targetAccountIdNumber}`; // マッチするものが見つからない場合 +} + +// 階層(number)を対応する階層(string)に変換する +function getMigrationTypeByNumber(numberValue: number): string { + switch (numberValue) { + case 1: + return MIGRATION_TYPE.ADMINISTRATOR; + case 2: + return MIGRATION_TYPE.BC; + case 3: + return MIGRATION_TYPE.DISTRIBUTOR; + case 4: + return MIGRATION_TYPE.DEALER; + case 5: + return MIGRATION_TYPE.CUSTOMER; + default: + return `NO_MATCHED_TIER_${numberValue}`; + } +} + +// 国(省略版)を対応する国(非省略版)に変換する +function getCountryLabelByValue(value: string): string { + const country = COUNTRY_LIST.find((country) => country.value === value); + return country ? country.label : `NO_MATCHED_COUNTRY_${value}`; +} + +// カードライセンス情報の突き合わせを行い、エラー時はエラー情報配列に情報追加する +function compareCardLicenses( + VerificationResultDetails: VerificationResultDetails[], + cardlicensesInputFiles: CardLicensesFile[], + cardLicenses: CardLicense[], + licenses: License[] +): boolean { + let isNoError = true; + + let row = 1; // カードライセンスファイルの行数 + for (const cardlicensesInputFile of cardlicensesInputFiles) { + const filterdCardLicenses = cardLicenses.filter( + (cardLicenses) => + cardLicenses.card_license_key === cardlicensesInputFile.card_license_key + ); + + if (filterdCardLicenses.length === 0) { + const VerificationResultDetailsOne: VerificationResultDetails = { + input: "cardLicenses", + inputRow: row, + diffTargetTable: "cardLicenses", + columnName: "card_license_key", + fileData: cardlicensesInputFile.card_license_key, + databaseData: "-", + reason: "レコード無し", + }; + VerificationResultDetails.push(VerificationResultDetailsOne); + isNoError = false; + continue; + } + + /* issue_idは自動採番のため比較しない + if (cardlicensesInputFile.issue_id !== filterdCardLicenses[0].issue_id) { + const VerificationResultDetailsOne: VerificationResultDetails = { + input: "cardLicenses", + diffTargetTable: "cardLicenses", + columnName: "issue_id", + fileData: cardlicensesInputFile.issue_id.toString(), + databaseData: filterdCardLicenses[0].issue_id.toString(), + reason: "内容不一致", + }; + VerificationResultDetails.push(VerificationResultDetailsOne); + isNoError = false; + } + */ + + const formattedActivated = getFormattedDate( + filterdCardLicenses[0].activated_at, + `yyyy/MM/dd hh:mm:ss` + ); + if (cardlicensesInputFile.activated_at !== formattedActivated) { + const VerificationResultDetailsOne: VerificationResultDetails = { + input: "cardLicenses", + inputRow: row, + diffTargetTable: "cardLicenses", + columnName: "activated_at", + fileData: cardlicensesInputFile.activated_at, + databaseData: formattedActivated, + reason: "内容不一致", + }; + VerificationResultDetails.push(VerificationResultDetailsOne); + isNoError = false; + } + + const filterdLicenses = licenses.filter( + (licenses) => licenses.id === filterdCardLicenses[0].license_id + ); + if (filterdLicenses.length === 0) { + const VerificationResultDetailsOne: VerificationResultDetails = { + input: "cardLicenses", + inputRow: row, + diffTargetTable: "licenses", + columnName: "id", + fileData: filterdCardLicenses[0].license_id.toString(), + databaseData: "-", + reason: "紐つくライセンスのレコード無し", + }; + VerificationResultDetails.push(VerificationResultDetailsOne); + isNoError = false; + continue; + } + + if (filterdLicenses[0].expiry_date !== null) { + const VerificationResultDetailsOne: VerificationResultDetails = { + input: "cardLicenses", + inputRow: row, + diffTargetTable: "licenses", + columnName: "expiry_date", + fileData: null, + databaseData: getFormattedDate( + filterdLicenses[0].expiry_date, + `yyyy/MM/dd hh:mm:ss` + ), + reason: "内容不一致", + }; + VerificationResultDetails.push(VerificationResultDetailsOne); + isNoError = false; + } + + if (filterdLicenses[0].account_id !== AUTO_INCREMENT_START) { + const VerificationResultDetailsOne: VerificationResultDetails = { + input: "cardLicenses", + inputRow: row, + diffTargetTable: "licenses", + columnName: "account_id", + fileData: AUTO_INCREMENT_START.toString(), + databaseData: filterdLicenses[0].account_id.toString(), + reason: "内容不一致", + }; + VerificationResultDetails.push(VerificationResultDetailsOne); + isNoError = false; + } + + if (filterdLicenses[0].type !== "CARD") { + const VerificationResultDetailsOne: VerificationResultDetails = { + input: "cardLicenses", + inputRow: row, + diffTargetTable: "licenses", + columnName: "type", + fileData: "CARD", + databaseData: filterdLicenses[0].type, + reason: "内容不一致", + }; + VerificationResultDetails.push(VerificationResultDetailsOne); + isNoError = false; + } + + if (filterdLicenses[0].status !== "Unallocated") { + const VerificationResultDetailsOne: VerificationResultDetails = { + input: "cardLicenses", + inputRow: row, + diffTargetTable: "licenses", + columnName: "status", + fileData: "Unallocated", + databaseData: filterdLicenses[0].status, + reason: "内容不一致", + }; + VerificationResultDetails.push(VerificationResultDetailsOne); + isNoError = false; + } + if (filterdLicenses[0].allocated_user_id !== null) { + const VerificationResultDetailsOne: VerificationResultDetails = { + input: "cardLicenses", + inputRow: row, + diffTargetTable: "licenses", + columnName: "allocated_user_id", + fileData: null, + databaseData: filterdLicenses[0].allocated_user_id.toString(), + reason: "内容不一致", + }; + VerificationResultDetails.push(VerificationResultDetailsOne); + isNoError = false; + } + if (filterdLicenses[0].order_id !== null) { + const VerificationResultDetailsOne: VerificationResultDetails = { + input: "cardLicenses", + inputRow: row, + diffTargetTable: "licenses", + columnName: "order_id", + fileData: null, + databaseData: filterdLicenses[0].order_id.toString(), + reason: "内容不一致", + }; + VerificationResultDetails.push(VerificationResultDetailsOne); + isNoError = false; + } + if (filterdLicenses[0].deleted_at !== null) { + const VerificationResultDetailsOne: VerificationResultDetails = { + input: "cardLicenses", + inputRow: row, + diffTargetTable: "licenses", + columnName: "deleted_at", + fileData: null, + databaseData: getFormattedDate( + filterdLicenses[0].deleted_at, + `yyyy/MM/dd hh:mm:ss` + ), + reason: "内容不一致", + }; + VerificationResultDetails.push(VerificationResultDetailsOne); + isNoError = false; + } + if (filterdLicenses[0].delete_order_id !== null) { + const VerificationResultDetailsOne: VerificationResultDetails = { + input: "cardLicenses", + inputRow: row, + diffTargetTable: "licenses", + columnName: "delete_order_id", + fileData: null, + databaseData: filterdLicenses[0].delete_order_id.toString(), + reason: "内容不一致", + }; + VerificationResultDetails.push(VerificationResultDetailsOne); + isNoError = false; + } + row = row + 1; + } + return isNoError; +} + +// ライセンス情報の突き合わせを行い、エラー時はエラー情報配列に情報追加する +function compareLicenses( + VerificationResultDetails: VerificationResultDetails[], + licensesFromFile: csvInputFileWithRow[], + licensesFromDatabase: License[], + accountsMappingInputFiles: AccountsMappingFile[] +): boolean { + let isNoError = true; + for (let i = 0; i < licensesFromFile.length; i++) { + if ( + !licensesFromDatabase[i] || + licensesFromFile[i].account_id !== + findAccountIdText( + accountsMappingInputFiles, + licensesFromDatabase[i].account_id + ) + ) { + const VerificationResultDetailsOne: VerificationResultDetails = { + input: "Account_transition", + inputRow: licensesFromFile[i].row, + diffTargetTable: "licenses", + columnName: "account_id", + fileData: licensesFromFile[i].account_id, + databaseData: licensesFromDatabase[i] + ? findAccountIdText( + accountsMappingInputFiles, + licensesFromDatabase[i].account_id + ) + `(${licensesFromDatabase[i].account_id})` + : "undifined", + reason: "内容不一致", + }; + VerificationResultDetails.push(VerificationResultDetailsOne); + isNoError = false; + } + if ( + !licensesFromDatabase[i] || + licensesFromFile[i].expired_date !== + getFormattedDate( + licensesFromDatabase[i].expiry_date, + `yyyy/MM/dd hh:mm:ss` + ) + ) { + const VerificationResultDetailsOne: VerificationResultDetails = { + input: "Account_transition", + inputRow: licensesFromFile[i].row, + diffTargetTable: "licenses", + columnName: "expired_date", + fileData: licensesFromFile[i].expired_date, + databaseData: licensesFromDatabase[i] + ? getFormattedDate( + licensesFromDatabase[i].expiry_date, + `yyyy/MM/dd hh:mm:ss` + ) + : "undifined", + reason: "内容不一致", + }; + VerificationResultDetails.push(VerificationResultDetailsOne); + isNoError = false; + } + } + return isNoError; +} + +// アカウント情報の突き合わせを行い、エラー時はエラー情報配列に情報追加する +function compareAccounts( + VerificationResultDetails: VerificationResultDetails[], + accountsFromFile: csvInputFileWithRow[], + countriesFromFile: csvInputFileWithRow[], + accountsFromDatabase: Account[], + accountsMappingInputFiles: AccountsMappingFile[] +): boolean { + let isNoError = true; + for (const accountFromFile of accountsFromFile) { + // DBレコードの存在チェック + const filterdAccounts = accountsFromDatabase.filter( + (accountsFromDatabase) => + findAccountIdText( + accountsMappingInputFiles, + accountsFromDatabase.id + ) === accountFromFile.account_id + ); + + if (filterdAccounts.length === 0) { + const VerificationResultDetailsOne: VerificationResultDetails = { + input: "Account_transition", + inputRow: accountFromFile.row, + diffTargetTable: "accounts", + columnName: "account_id", + fileData: accountFromFile.account_id, + databaseData: "-", + reason: "レコード無し", + }; + VerificationResultDetails.push(VerificationResultDetailsOne); + isNoError = false; + continue; + } + + // 項目チェック(parent_account_id) + const transratedParentId = transrateCountryHierarchy( + countriesFromFile, + accountFromFile.parent_id + ); + if ( + transratedParentId !== + findAccountIdText( + accountsMappingInputFiles, + filterdAccounts[0].parent_account_id + ) + ) { + const VerificationResultDetailsOne: VerificationResultDetails = { + input: "Account_transition", + inputRow: accountFromFile.row, + diffTargetTable: "accounts", + columnName: "parent_account_id", + fileData: + transratedParentId === accountFromFile.parent_id + ? accountFromFile.parent_id + : `${transratedParentId}(${accountFromFile.parent_id})`, + databaseData: + findAccountIdText( + accountsMappingInputFiles, + filterdAccounts[0].parent_account_id + ) + `(${filterdAccounts[0].parent_account_id})`, + reason: "内容不一致", + }; + VerificationResultDetails.push(VerificationResultDetailsOne); + isNoError = false; + continue; + } + + // 項目チェック(tier) + if ( + accountFromFile.type !== getMigrationTypeByNumber(filterdAccounts[0].tier) + ) { + const VerificationResultDetailsOne: VerificationResultDetails = { + input: "Account_transition", + inputRow: accountFromFile.row, + diffTargetTable: "accounts", + columnName: "tier", + fileData: accountFromFile.type, + databaseData: getMigrationTypeByNumber(filterdAccounts[0].tier), + reason: "内容不一致", + }; + VerificationResultDetails.push(VerificationResultDetailsOne); + isNoError = false; + continue; + } + + // 項目チェック(country) + if ( + accountFromFile.country !== + getCountryLabelByValue(filterdAccounts[0].country) + ) { + const VerificationResultDetailsOne: VerificationResultDetails = { + input: "Account_transition", + inputRow: accountFromFile.row, + diffTargetTable: "accounts", + columnName: "country", + fileData: accountFromFile.country, + databaseData: getCountryLabelByValue(filterdAccounts[0].country), + reason: "内容不一致", + }; + VerificationResultDetails.push(VerificationResultDetailsOne); + isNoError = false; + continue; + } + + // 項目チェック(company_name) + if (accountFromFile.company_name !== filterdAccounts[0].company_name) { + const VerificationResultDetailsOne: VerificationResultDetails = { + input: "Account_transition", + inputRow: accountFromFile.row, + diffTargetTable: "accounts", + columnName: "company_name", + fileData: accountFromFile.company_name, + databaseData: filterdAccounts[0].company_name, + reason: "内容不一致", + }; + VerificationResultDetails.push(VerificationResultDetailsOne); + isNoError = false; + continue; + } + } + return isNoError; +} 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 96d5030..d73563a 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 @@ -163,4 +163,21 @@ export class AccountsRepositoryService { ); }); } + + /** + * アカウント情報を全件取得する + * @returns Account[] + */ + async getAllAccounts( + context: Context, + ): Promise { + return await this.dataSource.transaction(async (entityManager) => { + const accountsRepo = entityManager.getRepository(Account); + + const accouts = accountsRepo.find({ + comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + }); + return accouts; + }); + } } 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 df1c190..7a9f46d 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 @@ -166,4 +166,34 @@ export class LicensesRepositoryService { return {}; }); } + + /** + * ライセンス情報を全件取得する + * @returns License[] + */ + async getAllLicenses(context: Context): Promise { + return await this.dataSource.transaction(async (entityManager) => { + const licenseRepo = entityManager.getRepository(License); + + const licenses = licenseRepo.find({ + comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + }); + return licenses; + }); + } + + /** + * カードライセンス情報を全件取得する + * @returns CardLicense[] + */ + async getAllCardLicense(context: Context): Promise { + return await this.dataSource.transaction(async (entityManager) => { + const cardLicenseRepo = entityManager.getRepository(CardLicense); + + const cardLicenses = cardLicenseRepo.find({ + comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + }); + return cardLicenses; + }); + } } diff --git a/data_migration_tools/server/src/repositories/users/users.repository.service.ts b/data_migration_tools/server/src/repositories/users/users.repository.service.ts index ebb4dc3..d060de7 100644 --- a/data_migration_tools/server/src/repositories/users/users.repository.service.ts +++ b/data_migration_tools/server/src/repositories/users/users.repository.service.ts @@ -138,4 +138,20 @@ export class UsersRepositoryService { await deleteEntity(usersRepo, { id: userId }, this.isCommentOut, context); }); } + + /** + * ユーザー情報を全件取得する + * @returns User[] + */ + async getAllUsers(context: Context): Promise { + return await this.dataSource.transaction(async (entityManager) => { + const userRepo = entityManager.getRepository(User); + + const users = userRepo.find({ + comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + }); + return users; + }); + } } + From 6d56255a5a44040fe764b4a863efdf4d061adaa9 Mon Sep 17 00:00:00 2001 From: "maruyama.t" Date: Wed, 28 Feb 2024 09:04:36 +0000 Subject: [PATCH 3/4] =?UTF-8?q?Merged=20PR=20792:=20parent=5Faccount=5Fid?= =?UTF-8?q?=E3=81=8C=E6=AD=A3=E3=81=97=E3=81=8F=E8=A8=AD=E5=AE=9A=E3=81=95?= =?UTF-8?q?=E3=82=8C=E3=81=AA=E3=81=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3804: parent_account_idが正しく設定されない](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3804) Map配列からaccountidをキーにdealerAccountIdを取る処理で、検索keyが逆になっていたため修正。 ## レビューポイント - とくになし ## 動作確認状況 - ローカルで確認(階層を付け替えたアカウントの親子階層が正しいことを確認) ## 補足 - 相談、参考資料などがあれば --- .../server/src/features/transfer/transfer.service.ts | 3 +-- 1 file changed, 1 insertion(+), 2 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 cf8dd53..34102cb 100644 --- a/data_migration_tools/server/src/features/transfer/transfer.service.ts +++ b/data_migration_tools/server/src/features/transfer/transfer.service.ts @@ -247,8 +247,7 @@ export class TransferService { // Countryのレコードは除外する if (account.type !== MIGRATION_TYPE.COUNTRY) { const dealerAccountId = - dealerRecords.get(account.dealerAccountId) ?? - account.dealerAccountId; + dealerRecords.get(account.accountId) ?? account.dealerAccountId; const type = this.getAccountType(account.type); const newAccount: AccountsFile = { accountId: account.accountId, From ce6e09a7d0dc3494bebdd5f360f7b609464cf418 Mon Sep 17 00:00:00 2001 From: "saito.k" Date: Thu, 29 Feb 2024 01:16:15 +0000 Subject: [PATCH 4/4] =?UTF-8?q?Merged=20PR=20791:=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=AE=E5=8F=96?= =?UTF-8?q?=E5=BE=97=E4=BB=B6=E6=95=B0=E3=81=8C10=E4=BB=B6=E3=81=A8?= =?UTF-8?q?=E3=81=AA=E3=81=A3=E3=81=A6=E3=81=84=E3=82=8B=E3=83=90=E3=82=B0?= =?UTF-8?q?=E3=81=AE=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3815: タスク一覧画面の取得件数が10件となっているバグの対応](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3815) - OptionItemのソート順を変換処理の中で行うように修正 - OrderByでソートするのはfindメソッドの作り的に無理そうなので - 調査にも時間がかかるため ## レビューポイント - タスクの中にこのバグが発生した原因を記載し、なぜこの修正にしたのか記述したのでそちらを確認していただいて変なところがあれば指摘していただきたいです。 - 書いている内容がよくわからない場合は、ハドルでの説明をさせてください。 ## UIの変更 - Before/Afterのスクショなど - スクショ置き場 ## 動作確認状況 - ローカルで確認 ## 補足 - 相談、参考資料などがあれば --- .../src/features/tasks/types/convert.ts | 18 ++++++++++++------ .../tasks/tasks.repository.service.ts | 13 ------------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/dictation_server/src/features/tasks/types/convert.ts b/dictation_server/src/features/tasks/types/convert.ts index 5353542..2ce6d99 100644 --- a/dictation_server/src/features/tasks/types/convert.ts +++ b/dictation_server/src/features/tasks/types/convert.ts @@ -79,12 +79,18 @@ const createTask = ( const createAudioOptionItems = ( optionItems: AudioOptionItemEntity[], ): AudioOptionItem[] => { - return optionItems.map((x) => { - return { - optionItemLabel: x.label, - optionItemValue: x.value, - }; - }); + // バグ 3786: [FB対応]タスク一覧画面のOptionItemがソート条件によって表示順がおかしくなる の対応 + // 並び順をID順に固定する + // 本来はRepository側でソートするべきだが、TYPEORMの仕様でソートすると取得件数が想定通りに取得できないため、ここでソートする + // 詳細は タスク 3815: タスク一覧画面の取得件数が10件となっているバグの対応 + return optionItems + .sort((a: AudioOptionItemEntity, b: AudioOptionItemEntity) => a.id - b.id) + .map((x) => { + return { + optionItemLabel: x.label, + optionItemValue: x.value, + }; + }); }; // Repository側のDTOからAssigneeオブジェクトを構築する diff --git a/dictation_server/src/repositories/tasks/tasks.repository.service.ts b/dictation_server/src/repositories/tasks/tasks.repository.service.ts index e2bbefa..a95a8ab 100644 --- a/dictation_server/src/repositories/tasks/tasks.repository.service.ts +++ b/dictation_server/src/repositories/tasks/tasks.repository.service.ts @@ -1462,91 +1462,78 @@ 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漏れが発生した場合に型エラーになるようにする