## 概要 [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というファイルを見るようにしています。
201 lines
6.9 KiB
TypeScript
201 lines
6.9 KiB
TypeScript
import {
|
||
Body,
|
||
Controller,
|
||
HttpStatus,
|
||
Post,
|
||
Req,
|
||
HttpException,
|
||
Logger,
|
||
} from "@nestjs/common";
|
||
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 { makeContext } from "../../common/log";
|
||
import { csvInputFile } from "../../common/types/types";
|
||
import { makeErrorResponse } from "src/common/errors/makeErrorResponse";
|
||
import { AUTO_INCREMENT_START } from "../../constants";
|
||
@ApiTags("transfer")
|
||
@Controller("transfer")
|
||
export class TransferController {
|
||
private readonly logger = new Logger(TransferController.name);
|
||
constructor(private readonly transferService: TransferService) {}
|
||
|
||
@Post()
|
||
@ApiResponse({
|
||
status: HttpStatus.OK,
|
||
type: transferResponse,
|
||
description: "成功時のレスポンス",
|
||
})
|
||
@ApiResponse({
|
||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||
description: "想定外のサーバーエラー",
|
||
})
|
||
@ApiOperation({ operationId: "dataRegist" })
|
||
async dataRegist(
|
||
@Body() body: transferRequest,
|
||
@Req() req: Request
|
||
): Promise<transferResponse> {
|
||
const context = makeContext("iko", "transfer");
|
||
|
||
const inputFilePath = body.inputFilePath;
|
||
|
||
this.logger.log(
|
||
`[IN] [${context.getTrackingId()}] ${
|
||
this.dataRegist.name
|
||
} | params: { inputFilePath: ${inputFilePath}};`
|
||
);
|
||
try {
|
||
// 読み込みファイルのフルパス
|
||
const accouncsFileFullPath = inputFilePath + "Account_transition.csv";
|
||
|
||
// ファイル存在チェックと読み込み
|
||
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(accouncsFileFullPath, "utf-8");
|
||
|
||
// レコードごとに分割
|
||
const csvInputFileLines = inputFile.split("\n");
|
||
|
||
// ヘッダー行を削除
|
||
csvInputFileLines.shift();
|
||
|
||
// 項目ごとに切り分ける
|
||
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],
|
||
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);
|
||
|
||
// account_idを通番に変換し、変換前account_id: 変換後accountId配列を作成する。
|
||
const accountIdList = csvInputFile.map((line) => line.account_id);
|
||
const accountIdListSet = new Set(accountIdList);
|
||
const accountIdListArray = Array.from(accountIdListSet);
|
||
const accountIdMap = new Map<string, number>();
|
||
accountIdListArray.forEach((accountId, index) => {
|
||
accountIdMap.set(accountId, index + AUTO_INCREMENT_START);
|
||
});
|
||
// CSVファイルの変換
|
||
const transferResponseCsv = await this.transferService.transferInputData(
|
||
context,
|
||
csvInputFile,
|
||
accountIdMap
|
||
);
|
||
|
||
// countryを除いた階層の再配置
|
||
const AccountsFileTypeLines = transferResponseCsv.accountsFileTypeLines;
|
||
const AccountsFile = await this.transferService.relocateHierarchy(
|
||
context,
|
||
AccountsFileTypeLines
|
||
);
|
||
const UsersFile = transferResponseCsv.usersFileLines;
|
||
const LicensesFile = transferResponseCsv.licensesFileLines;
|
||
// メールアドレスの重複を削除
|
||
const resultDuplicateEmail =
|
||
await this.transferService.removeDuplicateEmail(
|
||
context,
|
||
AccountsFile,
|
||
UsersFile,
|
||
LicensesFile
|
||
);
|
||
|
||
// transferResponseCsvを4つのJSONファイルの出力する(出力先はinputと同じにする)
|
||
const outputFilePath = body.inputFilePath;
|
||
const WorktypesFile = transferResponseCsv.worktypesFileLines;
|
||
this.transferService.outputJsonFile(
|
||
context,
|
||
outputFilePath,
|
||
resultDuplicateEmail.accountsFileLines,
|
||
resultDuplicateEmail.usersFileLines,
|
||
resultDuplicateEmail.licensesFileLines,
|
||
WorktypesFile
|
||
);
|
||
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.dataRegist.name}`
|
||
);
|
||
}
|
||
}
|
||
}
|