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というファイルを見るようにしています。
This commit is contained in:
parent
0ab6488f58
commit
f0d71937e3
@ -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 &&
|
||||
|
||||
@ -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" },
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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");
|
||||
}
|
||||
|
||||
// ライセンス・ワークタイプ・カードライセンスの登録
|
||||
|
||||
@ -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<void> {
|
||||
// パラメータ内容が長大なのでログには出さない
|
||||
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) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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<string, number>
|
||||
): Promise<registInputDataResponse> {
|
||||
// パラメータ内容が長大なのでログには出さない
|
||||
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<string, number> = 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<AccountsOutputFile[]> {
|
||||
accountsFileType: AccountsFileType[]
|
||||
): Promise<AccountsFile[]> {
|
||||
// パラメータ内容が長大なのでログには出さない
|
||||
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<number, number> = 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<void> {
|
||||
// パラメータ内容が長大なのでログには出さない
|
||||
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<string, string[]>();
|
||||
// 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<removeDuplicateEmailResponse> {
|
||||
// パラメータ内容が長大なのでログには出さない
|
||||
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}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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[];
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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<void> {
|
||||
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({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user