maruyama.t f0d71937e3 Merged PR 780: データ変換ツール(汚いデータ対応版)の作成+動作確認
## 概要
[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というファイルを見るようにしています。
2024-02-27 06:24:41 +00:00

201 lines
6.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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をつの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}`
);
}
}
}