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 ee38969..507ed3f 100644 --- a/data_migration_tools/server/src/features/transfer/transfer.controller.ts +++ b/data_migration_tools/server/src/features/transfer/transfer.controller.ts @@ -73,7 +73,8 @@ export class TransferController { const matchList = line.match(regExp); if (matchList) { matchList.forEach((match) => { - const replaced = match.replace(/,/g, " "); + // カンマを\に変換 + const replaced = match.replace(/,/g, "\\"); line = line.replace(match, replaced); }); } @@ -95,49 +96,50 @@ export class TransferController { HttpStatus.BAD_REQUEST ); } - - csvInputFile.push({ - type: data[0], - account_id: data[1], - parent_id: data[2], - email: data[3], - company_name: data[4], - first_name: data[5], - last_name: data[6], - country: data[7], - state: data[8], - start_date: data[9], - expired_date: data[10], - user_email: data[11], - author_id: data[12], - recording_mode: data[13], - wt1: data[14], - wt2: data[15], - wt3: data[16], - wt4: data[17], - wt5: data[18], - wt6: data[19], - wt7: data[20], - wt8: data[21], - wt9: data[22], - wt10: data[23], - wt11: data[24], - wt12: data[25], - wt13: data[26], - wt14: data[27], - wt15: data[28], - wt16: data[29], - wt17: data[30], - wt18: data[31], - wt19: data[32], - wt20: data[33], - }); + // data[1]がundefinedの場合、配列には格納しない + if (data[1] !== undefined) { + // バックスラッシュをカンマに戻す + data.forEach((value, index) => { + data[index] = value.replace(/\\/g, ","); + }); + csvInputFile.push({ + type: data[0], + account_id: data[1], + parent_id: data[2], + email: data[3], + company_name: data[4], + first_name: data[5], + last_name: data[6], + country: data[7], + state: data[8], + start_date: data[9], + expired_date: data[10], + user_email: data[11], + author_id: data[12], + recording_mode: data[13], + wt1: data[14], + wt2: data[15], + wt3: data[16], + wt4: data[17], + wt5: data[18], + wt6: data[19], + wt7: data[20], + wt8: data[21], + wt9: data[22], + wt10: data[23], + wt11: data[24], + wt12: data[25], + wt13: data[26], + wt14: data[27], + wt15: data[28], + wt16: data[29], + wt17: data[30], + wt18: data[31], + wt19: data[32], + wt20: data[33], + }); + } }); - // 最後の行がundefinedの場合はその行を削除 - if (csvInputFile[csvInputFile.length - 1].account_id === undefined) { - csvInputFile.pop(); - } - // 各データのバリデーションチェック await this.transferService.validateInputData(context, csvInputFile); @@ -153,12 +155,12 @@ export class TransferController { // アカウントID numberとstring対応表の出力 const accountsMappingFiles: AccountsMappingFile[] = []; accountIdMap.forEach((value, key) => { - const accountsMappingFile = new AccountsMappingFile(); + const accountsMappingFile = new AccountsMappingFile(); accountsMappingFile.accountIdNumber = value; - accountsMappingFile.accountIdText = key - accountsMappingFiles.push(accountsMappingFile) + accountsMappingFile.accountIdText = key; + accountsMappingFiles.push(accountsMappingFile); }); - + fs.writeFileSync( `${inputFilePath}account_map.json`, JSON.stringify(accountsMappingFiles) @@ -188,6 +190,13 @@ export class TransferController { LicensesFile ); + // AuthorIDが重複している場合通番を付与する + const transferDuplicateAuthorResultUsers = + await this.transferService.transferDuplicateAuthor( + context, + resultDuplicateEmail.usersFileLines + ); + // transferResponseCsvを4つのJSONファイルの出力する(出力先はinputと同じにする) const outputFilePath = body.inputFilePath; const WorktypesFile = transferResponseCsv.worktypesFileLines; @@ -195,7 +204,7 @@ export class TransferController { context, outputFilePath, resultDuplicateEmail.accountsFileLines, - resultDuplicateEmail.usersFileLines, + transferDuplicateAuthorResultUsers, resultDuplicateEmail.licensesFileLines, WorktypesFile ); 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 269f609..60b8d5c 100644 --- a/data_migration_tools/server/src/features/transfer/transfer.service.ts +++ b/data_migration_tools/server/src/features/transfer/transfer.service.ts @@ -54,6 +54,12 @@ export class TransferService { let userIdIndex = 0; // authorIdとuserIdの対応関係を保持するMapを定義 const authorIdToUserIdMap: Map = new Map(); + + // countryのリストを生成 + const countryAccounts = csvInputFile.filter( + (item) => item.type === "Country" + ); + // csvInputFileを一行読み込みする csvInputFile.forEach((line) => { // typeが"USER"以外の場合、アカウントデータの作成を行う @@ -105,8 +111,13 @@ export class TransferService { authorId: null, }); } else { - // typeが"USER"の場合 - if (line.type == MIGRATION_TYPE.USER) { + // typeが"USER"の場合、かつcountryのアカウントIDに所属していない場合 + if ( + line.type == MIGRATION_TYPE.USER && + !countryAccounts.some( + (countryAccount) => countryAccount.account_id === line.account_id + ) + ) { // line.author_idが存在する場合のみユーザーデータを作成する if (line.author_id) { // userIdIndexをインクリメントする @@ -229,46 +240,38 @@ export class TransferService { const relocatedAccounts: AccountsFile[] = []; const dealerRecords: Map = new Map(); - // accountsFileTypeをループ - accountsFileType.forEach((account) => { - // Distributorの場合はdealerを検索し、COUNTRYかチェックする - if (account.type === MIGRATION_TYPE.DISTRIBUTOR) { - const distributorParent = accountsFileType.find( - (a) => a.accountId === account.dealerAccountId - ); - if (distributorParent.type === MIGRATION_TYPE.COUNTRY) { - dealerRecords.set( - account.accountId, - distributorParent.dealerAccountId // Countryの親、BCのIDを設定 - ); + const countryAccounts = accountsFileType.filter( + (item) => item.type === MIGRATION_TYPE.COUNTRY + ); + + const notCountryAccounts = accountsFileType.filter( + (item) => item.type !== MIGRATION_TYPE.COUNTRY + ); + + notCountryAccounts.forEach((notCountryAccount) => { + let assignDealerAccountId = notCountryAccount.dealerAccountId; + // 親アカウントIDがcountryの場合、countryの親アカウントIDを設定する + for (const countryAccount of countryAccounts) { + if (countryAccount.accountId === notCountryAccount.dealerAccountId) { + assignDealerAccountId = countryAccount.dealerAccountId; } - } else { - dealerRecords.set(account.accountId, account.dealerAccountId); } - }); - // AccountsFileTypeのループを行い、階層情報の置換と新たな配列へのpushを行う - accountsFileType.forEach((account) => { - // Countryのレコードは除外する - if (account.type !== MIGRATION_TYPE.COUNTRY) { - const dealerAccountId = - dealerRecords.get(account.accountId) ?? 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, - }; + const assignType = this.getAccountType(notCountryAccount.type); - relocatedAccounts.push(newAccount); - } + const newAccount: AccountsFile = { + accountId: notCountryAccount.accountId, + type: assignType, + companyName: notCountryAccount.companyName, + country: notCountryAccount.country, + dealerAccountId: assignDealerAccountId, + adminName: notCountryAccount.adminName, + adminMail: notCountryAccount.adminMail, + userId: notCountryAccount.userId, + role: notCountryAccount.role, + authorId: notCountryAccount.authorId, + }; + relocatedAccounts.push(newAccount); }); return relocatedAccounts; @@ -364,6 +367,8 @@ export class TransferService { ); try { + // エラー配列を定義 + let errorArray: string[] = []; // アカウントに対するworktypeのMap配列を作成する const accountWorktypeMap = new Map(); // csvInputFileのバリデーションチェックを行う @@ -383,6 +388,13 @@ export class TransferService { HttpStatus.BAD_REQUEST ); } + // typeがUSER以外の場合で、countryがnullの場合エラー配列に格納する + if (line.type !== MIGRATION_TYPE.USER) { + if (!line.country) { + // countryがnullの場合エラー配列に格納する + errorArray.push(`country is null. index=${index}`); + } + } // countryのバリデーションチェック if (line.country) { if (!COUNTRY_LIST.find((country) => country.label === line.country)) { @@ -451,6 +463,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 + ); + } } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); throw new HttpException( @@ -603,4 +624,67 @@ export class TransferService { ); } } + + /** + * transferDuplicateAuthor + * @param usersFileLines: UsersFile[] + * @returns UsersFile[] + */ + async transferDuplicateAuthor( + context: Context, + usersFileLines: UsersFile[] + ): Promise { + // パラメータ内容が長大なのでログには出さない + this.logger.log( + `[IN] [${context.getTrackingId()}] ${this.transferDuplicateAuthor.name}` + ); + + try { + const newUsersFileLines: UsersFile[] = []; + + let processingAccountId: number = 0; //処理中のアカウントID + let duplicateSequence: number = 2; + let authorIdList: String[] = []; + for (const user of usersFileLines) { + if (user.accountId !== processingAccountId) { + //アカウントIDが別になった場合、通番を初期化する + duplicateSequence = 2; + processingAccountId = user.accountId; + authorIdList = []; + } + let assignAuthorId = user.authorId; + if (authorIdList.includes(user.authorId)) { + // 同じauthorIdがいる場合、自分のauthorIdに連番を付与する + assignAuthorId = assignAuthorId + duplicateSequence; + duplicateSequence = duplicateSequence + 1; + } + authorIdList.push(user.authorId); + + // 新しいAuthorIdのユーザに詰め替え + const newUser: UsersFile = { + accountId: user.accountId, + userId: user.userId, + name: user.name, + role: user.role, + authorId: assignAuthorId, + email: user.email, + }; + newUsersFileLines.push(newUser); + } + + return newUsersFileLines; + } 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.transferDuplicateAuthor.name + }` + ); + } + } } diff --git a/data_migration_tools/server/src/features/verification/verification.service.ts b/data_migration_tools/server/src/features/verification/verification.service.ts index 1de9596..4fda7d7 100644 --- a/data_migration_tools/server/src/features/verification/verification.service.ts +++ b/data_migration_tools/server/src/features/verification/verification.service.ts @@ -53,9 +53,11 @@ export class VerificationService { // 件数情報の取得 this.logger.log(`入力ファイルから件数情報を取得する`); - const accountCountFromFile = csvInputFiles.filter( + const accountFromFile = csvInputFiles.filter( (item) => item.type !== "USER" && item.type !== "Country" - ).length; + ); + const accountCountFromFile = accountFromFile.length; + const cardLicensesCountFromFile = cardlicensesInputFiles.length; const licensesCountFromFile = @@ -66,18 +68,35 @@ export class VerificationService { // 管理ユーザ数のカウント const administratorCountFromFile = accountCountFromFile; + // 一般ユーザ数のカウント - const normaluserCountFromFile = csvInputFiles.filter( - (item) => item.type === "USER" && item.user_email.length !== 0 - ).length; + // countryのアカウントに所属するユーザをカウント対象外とする + const countryAccountFromFile = csvInputFiles.filter( + (item) => item.type === "Country" + ); + + // USER、かつuser_emailが設定なし、かつcountryのアカウントID以外をユーザとする + const normaluserFromFile = csvInputFiles.filter( + (item) => + item.type === "USER" && + item.user_email.length !== 0 && + !countryAccountFromFile.some( + (countryItem) => countryItem.account_id === item.account_id + ) + ); + + const normaluserCountFromFile = normaluserFromFile.length; // ユーザ重複数のカウント let mailAdresses: string[] = []; - csvInputFiles.forEach((item) => { - // メールアドレスの要素を配列に追加(入力データとして管理者とユーザの両方に入ることはない) + accountFromFile.forEach((item) => { + // メールアドレスの要素を配列に追加 if (item.email.length !== 0) { mailAdresses.push(item.email); } + }); + normaluserFromFile.forEach((item) => { + // メールアドレスの要素を配列に追加 if (item.user_email.length !== 0) { mailAdresses.push(item.user_email); } @@ -232,7 +251,11 @@ export class VerificationService { } // dateを任意のフォーマットに変換する -const getFormattedDate = (date: Date | null, format: string) => { +const getFormattedDate = ( + date: Date | null, + format: string, + padHours: boolean = false // trueの場合、hhについてゼロパディングする(00→0、01→1、23→23) +) => { if (!date) { return null; } @@ -244,9 +267,13 @@ const getFormattedDate = (date: Date | null, format: string) => { s: date.getSeconds(), }; + // hhの値をゼロパディングするかどうかのフラグを確認 + const hourSymbol = padHours ? "hh" : "h"; + const formatted = format.replace(/(M+|d+|h+|m+|s+)/g, (v) => ( - (v.length > 1 ? "0" : "") + symbol[v.slice(-1) as keyof typeof symbol] + (v.length > 1 && v !== hourSymbol ? "0" : "") + + symbol[v.slice(-1) as keyof typeof symbol] ).slice(-2) ); @@ -542,12 +569,15 @@ function compareLicenses( VerificationResultDetails.push(VerificationResultDetailsOne); isNoError = false; } + + // expiry_dateについて、時はゼロパディングした値で比較する(×01~09 ○1~9) if ( !licensesFromDatabase[i] || licensesFromFile[i].expired_date !== getFormattedDate( licensesFromDatabase[i].expiry_date, - `yyyy/MM/dd hh:mm:ss` + `yyyy/MM/dd hh:mm:ss`, + true ) ) { const VerificationResultDetailsOne: VerificationResultDetails = { @@ -559,7 +589,8 @@ function compareLicenses( databaseData: licensesFromDatabase[i] ? getFormattedDate( licensesFromDatabase[i].expiry_date, - `yyyy/MM/dd hh:mm:ss` + `yyyy/MM/dd hh:mm:ss`, + true ) : "undifined", reason: "内容不一致",