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:
maruyama.t 2024-02-27 06:24:41 +00:00
parent 0ab6488f58
commit f0d71937e3
11 changed files with 601 additions and 353 deletions

View File

@ -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 &&

View File

@ -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" },

View File

@ -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,

View File

@ -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");
}
// ライセンス・ワークタイプ・カードライセンスの登録

View File

@ -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) {

View File

@ -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をつのJSONファイルの出力する(出力先はinputと同じにする)
// transferResponseCsvつの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) {

View File

@ -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をBCCountryの親に付け替える
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と新たな親アカウントIDBCの組み合わせを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}`
);
}
}
}

View File

@ -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[];
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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({