Merge branch 'develop' into ccb

This commit is contained in:
SAITO-PC-3\saito.k 2024-03-06 11:23:50 +09:00
commit eda88aa048
28 changed files with 2049 additions and 454 deletions

View File

@ -26,6 +26,7 @@
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"cookie-parser": "^1.4.6",
"csv": "^6.3.6",
"multer": "^1.4.5-lts.1",
"mysql2": "^2.3.3",
"reflect-metadata": "^0.1.13",
@ -4049,6 +4050,35 @@
"node": ">= 8"
}
},
"node_modules/csv": {
"version": "6.3.6",
"resolved": "https://registry.npmjs.org/csv/-/csv-6.3.6.tgz",
"integrity": "sha512-jsEsX2HhGp7xiwrJu5srQavKsh+HUJcCi78Ar3m4jlmFKRoTkkMy7ZZPP+LnQChmaztW+uj44oyfMb59daAs/Q==",
"dependencies": {
"csv-generate": "^4.3.1",
"csv-parse": "^5.5.3",
"csv-stringify": "^6.4.5",
"stream-transform": "^3.3.0"
},
"engines": {
"node": ">= 0.1.90"
}
},
"node_modules/csv-generate": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/csv-generate/-/csv-generate-4.3.1.tgz",
"integrity": "sha512-7YeeJq+44/I/O5N2sr2qBMcHZXhpfe38eh7DOFxyMtYO+Pir7kIfgFkW5MPksqKqqR6+/wX7UGoZm1Ot11151w=="
},
"node_modules/csv-parse": {
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.5.3.tgz",
"integrity": "sha512-v0KW6C0qlZzoGjk6u5tLmVfyZxNgPGXZsWTXshpAgKVGmGXzaVWGdlCFxNx5iuzcXT/oJN1HHM9DZKwtAtYa+A=="
},
"node_modules/csv-stringify": {
"version": "6.4.5",
"resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.4.5.tgz",
"integrity": "sha512-SPu1Vnh8U5EnzpNOi1NDBL5jU5Rx7DVHr15DNg9LXDTAbQlAVAmEbVt16wZvEW9Fu9Qt4Ji8kmeCJ2B1+4rFTQ=="
},
"node_modules/date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
@ -8654,6 +8684,11 @@
"npm": ">=6"
}
},
"node_modules/stream-transform": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-3.3.0.tgz",
"integrity": "sha512-pG1NeDdmErNYKtvTpFayrEueAmL0xVU5wd22V7InGnatl4Ocq3HY7dcXIKj629kXvYQvglCC7CeDIGAlx1RNGA=="
},
"node_modules/streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
@ -13027,6 +13062,32 @@
"which": "^2.0.1"
}
},
"csv": {
"version": "6.3.6",
"resolved": "https://registry.npmjs.org/csv/-/csv-6.3.6.tgz",
"integrity": "sha512-jsEsX2HhGp7xiwrJu5srQavKsh+HUJcCi78Ar3m4jlmFKRoTkkMy7ZZPP+LnQChmaztW+uj44oyfMb59daAs/Q==",
"requires": {
"csv-generate": "^4.3.1",
"csv-parse": "^5.5.3",
"csv-stringify": "^6.4.5",
"stream-transform": "^3.3.0"
}
},
"csv-generate": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/csv-generate/-/csv-generate-4.3.1.tgz",
"integrity": "sha512-7YeeJq+44/I/O5N2sr2qBMcHZXhpfe38eh7DOFxyMtYO+Pir7kIfgFkW5MPksqKqqR6+/wX7UGoZm1Ot11151w=="
},
"csv-parse": {
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.5.3.tgz",
"integrity": "sha512-v0KW6C0qlZzoGjk6u5tLmVfyZxNgPGXZsWTXshpAgKVGmGXzaVWGdlCFxNx5iuzcXT/oJN1HHM9DZKwtAtYa+A=="
},
"csv-stringify": {
"version": "6.4.5",
"resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.4.5.tgz",
"integrity": "sha512-SPu1Vnh8U5EnzpNOi1NDBL5jU5Rx7DVHr15DNg9LXDTAbQlAVAmEbVt16wZvEW9Fu9Qt4Ji8kmeCJ2B1+4rFTQ=="
},
"date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
@ -16557,6 +16618,11 @@
"resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz",
"integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw=="
},
"stream-transform": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-3.3.0.tgz",
"integrity": "sha512-pG1NeDdmErNYKtvTpFayrEueAmL0xVU5wd22V7InGnatl4Ocq3HY7dcXIKj629kXvYQvglCC7CeDIGAlx1RNGA=="
},
"streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",

View File

@ -45,7 +45,8 @@
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.0",
"swagger-cli": "^4.0.4",
"typeorm": "^0.3.20"
"typeorm": "^0.3.20",
"csv": "^6.3.6"
},
"devDependencies": {
"@types/express": "^4.17.17",

View File

@ -27,6 +27,10 @@ import { DeleteService } from "./features/delete/delete.service";
import { TransferModule } from "./features/transfer/transfer.module";
import { TransferController } from "./features/transfer/transfer.controller";
import { TransferService } from "./features/transfer/transfer.service";
import { VerificationController } from "./features/verification/verification.controller";
import { VerificationService } from "./features/verification/verification.service";
import { VerificationModule } from "./features/verification/verification.module";
@Module({
imports: [
ServeStaticModule.forRoot({
@ -41,6 +45,7 @@ import { TransferService } from "./features/transfer/transfer.service";
UsersModule,
TransferModule,
RegisterModule,
VerificationModule,
AccountsRepositoryModule,
UsersRepositoryModule,
SortCriteriaRepositoryModule,
@ -70,6 +75,7 @@ import { TransferService } from "./features/transfer/transfer.service";
UsersController,
DeleteController,
TransferController,
VerificationController,
],
providers: [
RegisterService,
@ -77,6 +83,7 @@ import { TransferService } from "./features/transfer/transfer.service";
UsersService,
DeleteService,
TransferService,
VerificationService,
],
})
export class AppModule {

View File

@ -18,7 +18,8 @@ export const makePassword = (): string => {
let autoGeneratedPassword: string = "";
while (!valid) {
// パスワードをランダムに決定
autoGeneratedPassword = "";
// パスワードをランダムに決定+
while (autoGeneratedPassword.length < passLength) {
// 上で決定したcharsの中からランダムに1文字ずつ追加
const index = Math.floor(Math.random() * chars.length);
@ -30,6 +31,11 @@ export const makePassword = (): string => {
valid =
autoGeneratedPassword.length == passLength &&
charaTypePattern.test(autoGeneratedPassword);
if (!valid) {
// autoGeneratedPasswordをログに出す
console.log("Password is not valid");
console.log(autoGeneratedPassword);
}
}
return autoGeneratedPassword;
};

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,12 @@ export class csvInputFile {
wt19: string;
wt20: string;
}
export class AccountsOutputFileStep1 {
export class csvInputFileWithRow extends csvInputFile {
row: number;
}
export class AccountsFileType {
accountId: number;
type: string;
companyName: string;
@ -43,9 +48,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 +61,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 +74,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 +82,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 +98,26 @@ export class CardLicensesInputFile {
updated_by?: string;
}
export function isAccountsInputFileArray(obj: any): obj is AccountsInputFile[] {
return Array.isArray(obj) && obj.every((item) => isAccountsInputFile(item));
export class AccountsMappingFile {
accountIdText: string;
accountIdNumber: number;
}
export function isAccountsInputFile(obj: any): obj is AccountsInputFile {
export class VerificationResultDetails {
input: string;
inputRow: number;
diffTargetTable: string;
columnName: string;
fileData: string;
databaseData: string;
reason: string;
}
export function isAccountsFileArray(obj: any): obj is AccountsFile[] {
return Array.isArray(obj) && obj.every((item) => isAccountsFile(item));
}
export function isAccountsFile(obj: any): obj is AccountsFile {
return (
typeof obj === "object" &&
obj !== null &&
@ -141,14 +137,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 +169,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 +189,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 +203,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 &&
@ -229,3 +223,65 @@ export function isCardLicensesInputFile(
(obj.updated_by === null || typeof obj.updated_by === "string")
);
}
export function isAccountsMappingFileArray(
obj: any
): obj is AccountsMappingFile[] {
return Array.isArray(obj) && obj.every((item) => isAccountsMappingFile(item));
}
export function isAccountsMappingFile(obj: any): obj is AccountsMappingFile {
return (
typeof obj === "object" &&
obj !== null &&
"accountIdText" in obj &&
"accountIdNumber" in obj &&
typeof obj.accountIdText === "string" &&
typeof obj.accountIdNumber === "number"
);
}
export function isCsvInputFileForValidateArray(obj: any): obj is csvInputFile[] {
return (
Array.isArray(obj) && obj.every((item) => isCsvInputFileForValidate(item))
);
}
export function isCsvInputFileForValidate(obj: any): obj is csvInputFile {
return (
typeof obj === "object" &&
"type" in obj &&
"account_id" in obj &&
"parent_id" in obj &&
"email" in obj &&
"company_name" in obj &&
"first_name" in obj &&
"last_name" in obj &&
"country" in obj &&
"state" in obj &&
"start_date" in obj &&
"expired_date" in obj &&
"user_email" in obj &&
"author_id" in obj &&
"recording_mode" in obj &&
"wt1" in obj &&
"wt2" in obj &&
"wt3" in obj &&
"wt4" in obj &&
"wt5" in obj &&
"wt6" in obj &&
"wt7" in obj &&
"wt8" in obj &&
"wt9" in obj &&
"wt10" in obj &&
"wt11" in obj &&
"wt12" in obj &&
"wt13" in obj &&
"wt14" in obj &&
"wt15" in obj &&
"wt16" in obj &&
"wt17" in obj &&
"wt18" in obj &&
"wt19" in obj &&
"wt20" in obj
);
}

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,
@ -78,6 +79,7 @@ export class AccountsService {
HttpStatus.INTERNAL_SERVER_ERROR
);
}
this.logger.log("idpにユーザーを作成成功");
// メールアドレス重複エラー
if (isConflictError(externalUser)) {
@ -89,6 +91,7 @@ export class AccountsService {
HttpStatus.BAD_REQUEST
);
}
this.logger.log("メールアドレスは重複していません");
let account: Account;
let user: User;
@ -103,6 +106,7 @@ export class AccountsService {
type,
externalUser.sub,
role,
authorId,
accountId,
userId,
acceptedEulaVersion,
@ -136,6 +140,7 @@ export class AccountsService {
account.id,
country
);
this.logger.log("コンテナー作成成功");
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(

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,53 @@ 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) {
this.logger.log("ランダムパスワード生成開始");
// ランダムなパスワードを生成する
const ramdomPassword = makePassword();
this.logger.log("ランダムパスワード生成完了");
// 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");
}
this.logger.log("account生成開始");
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 +155,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 +187,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 +197,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 +207,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

@ -13,18 +13,9 @@ import { Request } from "express";
import { transferRequest, transferResponse } from "./types/types";
import { TransferService } from "./transfer.service";
import { makeContext } from "../../common/log";
import { csvInputFile } from "../../common/types/types";
import { csvInputFile, AccountsMappingFile } 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,49 +68,78 @@ 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, "");
});
csvInputFile.push({
type: data[0],
account_id: data[1],
parent_id: data[2],
email: data[3],
company_name: data[4],
first_name: data[5],
last_name: data[6],
country: data[7],
state: data[8],
start_date: new Date(data[9]),
expired_date: new Date(data[10]),
user_email: data[11],
author_id: data[12],
recording_mode: data[13],
wt1: data[14],
wt2: data[15],
wt3: data[16],
wt4: data[17],
wt5: data[18],
wt6: data[19],
wt7: data[20],
wt8: data[21],
wt9: data[22],
wt10: data[23],
wt11: data[24],
wt12: data[25],
wt13: data[26],
wt14: data[27],
wt15: data[28],
wt16: data[29],
wt17: data[30],
wt18: data[31],
wt19: data[32],
wt20: data[33],
});
// "\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
);
}
// data[1]がundefinedの場合、配列には格納しない
if (data[1] !== undefined) {
// バックスラッシュをカンマに戻す
data.forEach((value, index) => {
data[index] = value.replace(/\\/g, ",");
});
csvInputFile.push({
type: data[0],
account_id: data[1],
parent_id: data[2],
email: data[3],
company_name: data[4],
first_name: data[5],
last_name: data[6],
country: data[7],
state: data[8],
start_date: data[9],
expired_date: data[10],
user_email: data[11],
author_id: data[12],
recording_mode: data[13],
wt1: data[14],
wt2: data[15],
wt3: data[16],
wt4: data[17],
wt5: data[18],
wt6: data[19],
wt7: data[20],
wt8: data[21],
wt9: data[22],
wt10: data[23],
wt11: data[24],
wt12: data[25],
wt13: data[26],
wt14: data[27],
wt15: data[28],
wt16: data[29],
wt17: data[30],
wt18: data[31],
wt19: data[32],
wt20: data[33],
});
}
});
// 各データのバリデーションチェック
await this.transferService.validateInputData(context, csvInputFile);
@ -131,36 +151,63 @@ export class TransferController {
accountIdListArray.forEach((accountId, index) => {
accountIdMap.set(accountId, index + AUTO_INCREMENT_START);
});
// アカウントID numberとstring対応表の出力
const accountsMappingFiles: AccountsMappingFile[] = [];
accountIdMap.forEach((value, key) => {
const accountsMappingFile = new AccountsMappingFile();
accountsMappingFile.accountIdNumber = value;
accountsMappingFile.accountIdText = key;
accountsMappingFiles.push(accountsMappingFile);
});
fs.writeFileSync(
`${inputFilePath}account_map.json`,
JSON.stringify(accountsMappingFiles)
);
// 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と同じにする)
// AuthorIDが重複している場合通番を付与する
const transferDuplicateAuthorResultUsers =
await this.transferService.transferDuplicateAuthor(
context,
resultDuplicateEmail.accountsFileLines,
resultDuplicateEmail.usersFileLines
);
// 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,
transferDuplicateAuthorResultUsers,
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,40 +31,51 @@ 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 errorArray: string[] = [];
let userIdIndex = 0;
// authorIdとuserIdの対応関係を保持するMapを定義
const authorIdToUserIdMap: Map<string, number> = new Map();
// countryのリストを生成
const countryAccounts = csvInputFile.filter(
(item) => item.type === "Country"
);
// 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
)?.value;
// adminNameの変換(last_name + " "+ first_name)
const adminName = `${line.last_name} ${line.first_name}`;
// もしline.last_nameとline.first_nameが存在しない場合、line.admin_mailをnameにする
let adminName = line.email;
if (line.last_name && line.first_name) {
adminName = `${line.last_name} ${line.first_name}`;
// スペースが前後に入っている場合があるのでTrimする
adminName = adminName.trim();
}
// ランダムパスワードの生成(データ登録ツール側で行うのでやらない)
// common/password/password.tsのmakePasswordを使用
// const autoGeneratedPassword = makePassword();
@ -71,8 +86,18 @@ export class TransferService {
if (line.parent_id) {
parentAccountId = accountIdMap.get(line.parent_id);
}
// AccountsOutputFile配列にPush
accountsOutputFileStep1Lines.push({
// 万が一parent_idが入力されているのに存在しなかった場合は、エラー配列に追加する
if (parentAccountId === undefined) {
errorArray.push(
`parent_id is invalid. parent_id=${line.parent_id}`
);
}
// userIdIndexをインクリメントする
userIdIndex++;
// AccountsFile配列にPush
accountsFileTypeLines.push({
// accountIdはaccountIdMapから取得する
accountId: accountIdMap.get(line.account_id),
type: line.type,
@ -82,150 +107,180 @@ 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"の場合、かつcountryのアカウントIDに所属していない場合
if (
line.type == MIGRATION_TYPE.USER &&
!countryAccounts.some(
(countryAccount) => countryAccount.account_id === line.account_id
)
) {
// line.author_idが存在する場合のみユーザーデータを作成する
if (line.author_id) {
// userIdIndexをインクリメントする
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"のデータの場合はデモライセンスなので登録しない
if (line.expired_date !== "9999/12/31 23:59:59") {
// 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;
}
}
}
}
}
// つぎの行に進む
});
// エラー配列に値が存在する場合はエラーファイルを出力する
if (errorArray.length > 0) {
const errorFileJson = JSON.stringify(errorArray);
fs.writeFileSync(`error.json`, errorFileJson);
throw new HttpException(
`errorArray is invalid. errorArray=${errorArray}`,
HttpStatus.BAD_REQUEST
);
}
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 dealerRecords: Map<number, number> = new Map();
const countryAccounts = accountsFileType.filter(
(item) => item.type === MIGRATION_TYPE.COUNTRY
);
// 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;
const notCountryAccounts = accountsFileType.filter(
(item) => item.type !== MIGRATION_TYPE.COUNTRY
);
notCountryAccounts.forEach((notCountryAccount) => {
let assignDealerAccountId = notCountryAccount.dealerAccountId;
// 親アカウントIDがcountryの場合、countryの親アカウントIDを設定する
for (const countryAccount of countryAccounts) {
if (countryAccount.accountId === notCountryAccount.dealerAccountId) {
assignDealerAccountId = countryAccount.dealerAccountId;
}
}
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,
});
const assignType = this.getAccountType(notCountryAccount.type);
const newAccount: AccountsFile = {
accountId: notCountryAccount.accountId,
type: assignType,
companyName: notCountryAccount.companyName,
country: notCountryAccount.country,
dealerAccountId: assignDealerAccountId,
adminName: notCountryAccount.adminName,
adminMail: notCountryAccount.adminMail,
userId: notCountryAccount.userId,
role: notCountryAccount.role,
authorId: notCountryAccount.authorId,
};
relocatedAccounts.push(newAccount);
});
return accountsOutputFile;
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 +288,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 +328,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 +367,10 @@ export class TransferService {
);
try {
// エラー配列を定義
let errorArray: string[] = [];
// アカウントに対するworktypeのMap配列を作成する
const accountWorktypeMap = new Map<string, string[]>();
// csvInputFileのバリデーションチェックを行う
csvInputFile.forEach((line, index) => {
// typeのバリデーションチェック
@ -317,10 +388,16 @@ export class TransferService {
HttpStatus.BAD_REQUEST
);
}
// typeがUSER以外の場合で、countryがnullの場合エラー配列に格納する
if (line.type !== MIGRATION_TYPE.USER) {
if (!line.country) {
// countryがnullの場合エラー配列に格納する
errorArray.push(`country is null. index=${index}`);
}
}
// countryのバリデーションチェック
if (line.country) {
if (!COUNTRY_LIST.find((country) => country.label === line.country)) {
console.log(line.country);
throw new HttpException(
`country is invalid. index=${index} country=${line.country}`,
HttpStatus.BAD_REQUEST
@ -329,7 +406,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 +438,260 @@ 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]]);
}
}
}
});
// エラー配列に値が存在する場合はエラーファイルを出力する
if (errorArray.length > 0) {
const errorFileJson = JSON.stringify(errorArray);
fs.writeFileSync(`error.json`, errorFileJson);
throw new HttpException(
`errorArray is invalid. errorArray=${errorArray}`,
HttpStatus.BAD_REQUEST
);
}
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
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}`
);
}
}
/**
* transferDuplicateAuthor
* @param accountsFileLines: AccountsFile[]
* @param usersFileLines: UsersFile[]
* @returns UsersFile[]
*/
async transferDuplicateAuthor(
context: Context,
accountsFileLines: AccountsFile[],
usersFileLines: UsersFile[]
): Promise<UsersFile[]> {
// パラメータ内容が長大なのでログには出さない
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.transferDuplicateAuthor.name}`
);
try {
const newUsersFileLines: UsersFile[] = [];
for (const accountsFileLine of accountsFileLines) {
let duplicateSequence: number = 2;
let authorIdList: String[] = [];
// メールアドレス重複時はアカウントにもAuthorIdが設定されるので重複チェック用のリストに追加しておく
if (accountsFileLine.authorId) {
authorIdList.push(accountsFileLine.authorId);
}
const targetaccountUsers = usersFileLines.filter(
(item) => item.accountId === accountsFileLine.accountId
);
for (const targetaccountUser of targetaccountUsers) {
let assignAuthorId = targetaccountUser.authorId;
if (authorIdList.includes(targetaccountUser.authorId)) {
// 同じauthorIdがいる場合、自分のauthorIdに連番を付与する
assignAuthorId = assignAuthorId + duplicateSequence;
duplicateSequence = duplicateSequence + 1;
}
authorIdList.push(targetaccountUser.authorId);
// 新しいAuthorIdのユーザに詰め替え
const newUser: UsersFile = {
accountId: targetaccountUser.accountId,
userId: targetaccountUser.userId,
name: targetaccountUser.name,
role: targetaccountUser.role,
authorId: assignAuthorId,
email: targetaccountUser.email,
};
newUsersFileLines.push(newUser);
}
}
return newUsersFileLines;
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${
this.transferDuplicateAuthor.name
}`
);
}
}
}

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

@ -74,6 +74,9 @@ export class UsersService {
accountId,
authorId
);
this.logger.log(
`[${context.getTrackingId()}] isAuthorIdDuplicated=${isAuthorIdDuplicated}`
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
@ -88,9 +91,10 @@ export class UsersService {
);
}
}
this.logger.log("ランダムパスワード生成開始");
// ランダムなパスワードを生成する
const ramdomPassword = makePassword();
this.logger.log("ランダムパスワード生成完了");
//Azure AD B2Cにユーザーを新規登録する
let externalUser: { sub: string } | ConflictError;

View File

@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
export class VerificationRequest {
@ApiProperty()
inputFilePath: string;
}
export class VerificationResponse {}

View File

@ -0,0 +1,148 @@
import {
Body,
Controller,
HttpStatus,
Post,
Req,
Logger,
HttpException,
} from "@nestjs/common";
import { makeErrorResponse } from "../../common/error/makeErrorResponse";
import fs from "fs";
import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
import { Request } from "express";
import { VerificationRequest, VerificationResponse } from "./types/types";
import { VerificationService } from "./verification.service";
import { makeContext } from "../../common/log";
import {
csvInputFileWithRow,
isAccountsMappingFileArray,
isCardLicensesFileArray,
isCsvInputFileForValidateArray,
} from "../../common/types/types";
import * as csv from "csv";
@ApiTags("verification")
@Controller("verification")
export class VerificationController {
private readonly logger = new Logger(VerificationController.name);
constructor(private readonly verificationService: VerificationService) {}
@Post()
@ApiResponse({
status: HttpStatus.OK,
type: VerificationResponse,
description: "成功時のレスポンス",
})
@ApiResponse({
status: HttpStatus.INTERNAL_SERVER_ERROR,
description: "想定外のサーバーエラー",
})
@ApiOperation({ operationId: "dataVerification" })
async dataVerification(
@Body() body: VerificationRequest,
@Req() req: Request
): Promise<VerificationResponse> {
const context = makeContext("iko", "varification");
const inputFilePath = body.inputFilePath;
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.dataVerification.name
} | params: { inputFilePath: ${inputFilePath}};`
);
try {
// 読み込みファイルのフルパス
const accountTransitionFileFullPath =
inputFilePath + "Account_transition.csv";
const accountMapFileFullPath = inputFilePath + "account_map.json";
const cardLicensesFileFullPath = inputFilePath + "cardLicenses.json";
// ファイル存在チェックと読み込み
if (!fs.existsSync(accountTransitionFileFullPath)) {
this.logger.error(
`file not exists from ${accountTransitionFileFullPath}`
);
throw new Error(
`file not exists from ${accountTransitionFileFullPath}`
);
}
if (!fs.existsSync(accountMapFileFullPath)) {
this.logger.error(`file not exists from ${accountMapFileFullPath}`);
throw new Error(`file not exists from ${accountMapFileFullPath}`);
}
if (!fs.existsSync(cardLicensesFileFullPath)) {
this.logger.error(`file not exists from ${cardLicensesFileFullPath}`);
throw new Error(`file not exists from ${cardLicensesFileFullPath}`);
}
// カードライセンスの登録用ファイル読み込み
const cardLicensesObject = JSON.parse(
fs.readFileSync(cardLicensesFileFullPath, "utf8")
);
// 型ガードcardLicenses
if (!isCardLicensesFileArray(cardLicensesObject)) {
throw new Error("input file is not cardLicensesInputFiles");
}
// アカウントIDマッピング用ファイル読み込み
const accountsMapObject = JSON.parse(
fs.readFileSync(accountMapFileFullPath, "utf8")
);
// 型ガードaccountsMapingFile
if (!isAccountsMappingFileArray(accountsMapObject)) {
throw new Error("input file is not accountsMapingFile");
}
// 移行用csvファイルの読み込みcsv parse
fs.createReadStream(accountTransitionFileFullPath).pipe(
csv.parse({ columns: true, delimiter: "," }, (err, csvInputFiles) => {
// 型ガードcsvInputFile
if (!isCsvInputFileForValidateArray(csvInputFiles)) {
throw new Error("input file is not csvInputFile");
}
const csvInputFileswithRows: csvInputFileWithRow[] = [];
let rowCount = 2; // csvの何行目かを表す変数。ヘッダ行があるので2から開始
for (const csvInputFile of csvInputFiles) {
const csvInputFileswithRow: csvInputFileWithRow = {
...csvInputFile,
row: rowCount
};
csvInputFileswithRows.push(csvInputFileswithRow);
rowCount = rowCount + 1;
}
this.verificationService.varificationData(
context,
inputFilePath,
csvInputFileswithRows,
accountsMapObject,
cardLicensesObject
);
})
);
return {};
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.dataVerification.name}`
);
}
}
}
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}

View File

@ -0,0 +1,17 @@
import { Module } from "@nestjs/common";
import { VerificationController } from "./verification.controller";
import { VerificationService } from "./verification.service";
import { LicensesRepositoryModule } from "../../repositories/licenses/licenses.repository.module";
import { AccountsRepositoryModule } from "../../repositories/accounts/accounts.repository.module";
import { UsersRepositoryModule } from "../../repositories//users/users.repository.module";
@Module({
imports: [
LicensesRepositoryModule,
AccountsRepositoryModule,
UsersRepositoryModule,
],
controllers: [VerificationController],
providers: [VerificationService],
})
export class VerificationModule {}

View File

@ -0,0 +1,726 @@
import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common";
import { Context } from "../../common/log";
import {
AccountsMappingFile,
CardLicensesFile,
csvInputFileWithRow,
VerificationResultDetails,
} from "../../common/types/types";
import {
AUTO_INCREMENT_START,
MIGRATION_TYPE,
COUNTRY_LIST,
} from "../../constants/index";
import { makeErrorResponse } from "../../common/error/makeErrorResponse";
import { LicensesRepositoryService } from "../../repositories/licenses/licenses.repository.service";
import {
License,
CardLicense,
} from "../../repositories/licenses/entity/license.entity";
import { AccountsRepositoryService } from "../../repositories/accounts/accounts.repository.service";
import { UsersRepositoryService } from "../../repositories//users/users.repository.service";
import { Account } from "src/repositories/accounts/entity/account.entity";
import fs from "fs";
@Injectable()
export class VerificationService {
constructor(
private readonly AccountsRepository: AccountsRepositoryService,
private readonly UsersRepository: UsersRepositoryService,
private readonly licensesRepository: LicensesRepositoryService
) {}
private readonly logger = new Logger(VerificationService.name);
/**
* Verification Data
* @param inputFilePath: string
*/
async varificationData(
context: Context,
inputFilePath: string,
csvInputFiles: csvInputFileWithRow[],
accountsMappingInputFiles: AccountsMappingFile[],
cardlicensesInputFiles: CardLicensesFile[]
): Promise<void> {
// パラメータ内容が長大なのでログには出さない
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.varificationData.name}`
);
// this.logger.log(csvInputFiles);
try {
// 件数情報の取得
this.logger.log(`入力ファイルから件数情報を取得する`);
const accountFromFile = csvInputFiles.filter(
(item) => item.type !== "USER" && item.type !== "Country"
);
const accountCountFromFile = accountFromFile.length;
const cardLicensesCountFromFile = cardlicensesInputFiles.length;
const licensesCountFromFile =
csvInputFiles.filter(
(item) =>
item.type === "USER" && item.expired_date !== "9999/12/31 23:59:59"
).length + cardLicensesCountFromFile;
// 管理ユーザ数のカウント
const administratorCountFromFile = accountCountFromFile;
// 一般ユーザ数のカウント
// countryのアカウントに所属するユーザをカウント対象外とする
const countryAccountFromFile = csvInputFiles.filter(
(item) => item.type === "Country"
);
// USER、かつuser_emailが設定なし、かつcountryのアカウントID以外をユーザとする
const normaluserFromFile = csvInputFiles.filter(
(item) =>
item.type === "USER" &&
item.user_email.length !== 0 &&
!countryAccountFromFile.some(
(countryItem) => countryItem.account_id === item.account_id
)
);
const normaluserCountFromFile = normaluserFromFile.length;
// ユーザ重複数のカウント
let mailAdresses: string[] = [];
accountFromFile.forEach((item) => {
// メールアドレスの要素を配列に追加
if (item.email.length !== 0) {
mailAdresses.push(item.email);
}
});
normaluserFromFile.forEach((item) => {
// メールアドレスの要素を配列に追加
if (item.user_email.length !== 0) {
mailAdresses.push(item.user_email);
}
});
// 重複する要素を抽出
const duplicates: { [key: string]: number } = {};
mailAdresses.forEach((str) => {
duplicates[str] = (duplicates[str] || 0) + 1;
});
// 重複する要素と件数を表示
let duplicateCount = 0;
Object.keys(duplicates).forEach((key) => {
const count = duplicates[key];
if (count > 1) {
// 重複件数をカウント
duplicateCount = duplicateCount + (count - 1);
//console.log(`${key}が${count}件`);
}
});
const userCountFromFile =
administratorCountFromFile + normaluserCountFromFile - duplicateCount;
this.logger.log(`accountCountFromFile=${accountCountFromFile}`);
this.logger.log(`cardLicensesCountFromFile=${cardLicensesCountFromFile}`);
this.logger.log(`licensesCountFromFile=${licensesCountFromFile}`);
this.logger.log(`userCountFromFile=${userCountFromFile}`);
// DBから情報を取得する
this.logger.log(`DBの情報を取得する`);
const accounts = await this.AccountsRepository.getAllAccounts(context);
const users = await this.UsersRepository.getAllUsers(context);
const licenses = await this.licensesRepository.getAllLicenses(context);
const cardLicenses = await this.licensesRepository.getAllCardLicense(
context
);
// DB件数のカウント
this.logger.log(`DBの情報から件数を取得する`);
const accountsCountFromDB = accounts.length;
const usersCountFromDB = users.length;
const licensesCountFromDB = licenses.length;
const cardLicensesCountFromDB = cardLicenses.length;
this.logger.log(`accountsCountFromDB=${accountsCountFromDB}`);
this.logger.log(`usersCountFromDB=${usersCountFromDB}`);
this.logger.log(`licensesCountFromDB=${licensesCountFromDB}`);
this.logger.log(`cardLicensesCountFromDB=${cardLicensesCountFromDB}`);
// エラー情報の定義
const VerificationResultDetails: VerificationResultDetails[] = [];
// カードライセンス関連の情報突き合わせ
this.logger.log(`カードライセンス関連の情報突き合わせ`);
const isCardDetailNoError = compareCardLicenses(
VerificationResultDetails,
cardlicensesInputFiles,
cardLicenses,
licenses
);
// ライセンス関連の情報突き合わせ
this.logger.log(`ライセンス関連の情報突き合わせ`);
const isLicensesDetailNoError = compareLicenses(
VerificationResultDetails,
csvInputFiles.filter(
(item) =>
item.type === "USER" && item.expired_date !== "9999/12/31 23:59:59"
),
licenses.filter((item) => item.expiry_date !== null),
accountsMappingInputFiles
);
// アカウント情報の突き合わせ
this.logger.log(`アカウント関連の情報突き合わせ`);
const isAccountsDetailNoError = compareAccounts(
VerificationResultDetails,
csvInputFiles.filter(
(item) => item.type !== "USER" && item.type !== "Country"
),
csvInputFiles.filter((item) => item.type === "Country"),
accounts,
accountsMappingInputFiles
);
// 結果の判定と出力
this.logger.log(`結果の判定と出力`);
const isAccountCountNoDifference =
accountCountFromFile === accountsCountFromDB;
const isUsersCountNoDifference = userCountFromFile === usersCountFromDB;
const isLicensesCountNoDifference =
licensesCountFromFile === licensesCountFromDB;
const isCardLicensesCountNoDifference =
cardLicensesCountFromFile === cardLicensesCountFromDB;
const isNoDetailError = VerificationResultDetails.length === 0;
const isSummaryNoError =
isAccountCountNoDifference &&
isUsersCountNoDifference &&
isLicensesCountNoDifference &&
isCardLicensesCountNoDifference &&
isNoDetailError;
const summaryString = `
${isSummaryNoError ? "OK" : "NG"}
${
isAccountCountNoDifference ? "OK" : "NG"
}csv件数:${accountCountFromFile}/DB件数:${accountsCountFromDB}
${
isLicensesCountNoDifference ? "OK" : "NG"
}csv件数:${licensesCountFromFile}/DB件数:${licensesCountFromDB}
${
isCardLicensesCountNoDifference ? "OK" : "NG"
}csv件数:${cardLicensesCountFromFile}/DB件数:${cardLicensesCountFromDB}
${
isUsersCountNoDifference ? "OK" : "NG"
}csv件数:${userCountFromFile}/DB件数:${usersCountFromDB}
${isAccountsDetailNoError ? "OK" : "NG"}
${isCardDetailNoError ? "OK" : "NG"}
${isLicensesDetailNoError ? "OK" : "NG"}
`;
// サマリファイルの書き込み
fs.writeFileSync(`${inputFilePath}resultsummary.txt`, summaryString);
// 詳細ファイルの書き込み
// 配列をJSON文字列に変換
const jsonContent = JSON.stringify(VerificationResultDetails, null, 2);
// JSONをファイルに書き込み
fs.writeFileSync(`${inputFilePath}resultdetail.json`, jsonContent);
} 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.varificationData.name}`
);
}
}
}
// dateを任意のフォーマットに変換する
const getFormattedDate = (
date: Date | null,
format: string,
padHours: boolean = false // trueの場合、hhについてゼロパディングする00→0、01→1、23→23
) => {
if (!date) {
return null;
}
const symbol = {
M: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
m: date.getMinutes(),
s: date.getSeconds(),
};
// hhの値をゼロパディングするかどうかのフラグを確認
const hourSymbol = padHours ? "hh" : "h";
const formatted = format.replace(/(M+|d+|h+|m+|s+)/g, (v) =>
(
(v.length > 1 && v !== hourSymbol ? "0" : "") +
symbol[v.slice(-1) as keyof typeof symbol]
).slice(-2)
);
return formatted.replace(/(y+)/g, (v) =>
date.getFullYear().toString().slice(-v.length)
);
};
// 親の階層がcountryの場合、countryの親を返却する
function transrateCountryHierarchy(
countriesFromFile: csvInputFileWithRow[],
targetParentAccountIdString: string
): string {
for (const countryFromFile of countriesFromFile) {
if (countryFromFile.account_id === targetParentAccountIdString) {
return countryFromFile.parent_id;
}
}
return targetParentAccountIdString;
}
// アカウントIDnumberを対応するアカウントIDstringに変換する
function findAccountIdText(
accountsMappings: AccountsMappingFile[],
targetAccountIdNumber: number
): string {
if (targetAccountIdNumber == null) {
return "";
}
for (const accountsMapping of accountsMappings) {
if (accountsMapping.accountIdNumber === targetAccountIdNumber) {
return accountsMapping.accountIdText;
}
}
return `NO_MATCHED_ACCOUNTID_${targetAccountIdNumber}`; // マッチするものが見つからない場合
}
// 階層numberを対応する階層stringに変換する
function getMigrationTypeByNumber(numberValue: number): string {
switch (numberValue) {
case 1:
return MIGRATION_TYPE.ADMINISTRATOR;
case 2:
return MIGRATION_TYPE.BC;
case 3:
return MIGRATION_TYPE.DISTRIBUTOR;
case 4:
return MIGRATION_TYPE.DEALER;
case 5:
return MIGRATION_TYPE.CUSTOMER;
default:
return `NO_MATCHED_TIER_${numberValue}`;
}
}
// 国(省略版)を対応する国(非省略版)に変換する
function getCountryLabelByValue(value: string): string {
const country = COUNTRY_LIST.find((country) => country.value === value);
return country ? country.label : `NO_MATCHED_COUNTRY_${value}`;
}
// カードライセンス情報の突き合わせを行い、エラー時はエラー情報配列に情報追加する
function compareCardLicenses(
VerificationResultDetails: VerificationResultDetails[],
cardlicensesInputFiles: CardLicensesFile[],
cardLicenses: CardLicense[],
licenses: License[]
): boolean {
let isNoError = true;
let row = 1; // カードライセンスファイルの行数
for (const cardlicensesInputFile of cardlicensesInputFiles) {
const filterdCardLicenses = cardLicenses.filter(
(cardLicenses) =>
cardLicenses.card_license_key === cardlicensesInputFile.card_license_key
);
if (filterdCardLicenses.length === 0) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "cardLicenses",
columnName: "card_license_key",
fileData: cardlicensesInputFile.card_license_key,
databaseData: "-",
reason: "レコード無し",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
continue;
}
/* issue_id
if (cardlicensesInputFile.issue_id !== filterdCardLicenses[0].issue_id) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
diffTargetTable: "cardLicenses",
columnName: "issue_id",
fileData: cardlicensesInputFile.issue_id.toString(),
databaseData: filterdCardLicenses[0].issue_id.toString(),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
*/
const formattedActivated = getFormattedDate(
filterdCardLicenses[0].activated_at,
`yyyy/MM/dd hh:mm:ss`
);
if (cardlicensesInputFile.activated_at !== formattedActivated) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "cardLicenses",
columnName: "activated_at",
fileData: cardlicensesInputFile.activated_at,
databaseData: formattedActivated,
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
const filterdLicenses = licenses.filter(
(licenses) => licenses.id === filterdCardLicenses[0].license_id
);
if (filterdLicenses.length === 0) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "id",
fileData: filterdCardLicenses[0].license_id.toString(),
databaseData: "-",
reason: "紐つくライセンスのレコード無し",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
continue;
}
if (filterdLicenses[0].expiry_date !== null) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "expiry_date",
fileData: null,
databaseData: getFormattedDate(
filterdLicenses[0].expiry_date,
`yyyy/MM/dd hh:mm:ss`
),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
if (filterdLicenses[0].account_id !== AUTO_INCREMENT_START) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "account_id",
fileData: AUTO_INCREMENT_START.toString(),
databaseData: filterdLicenses[0].account_id.toString(),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
if (filterdLicenses[0].type !== "CARD") {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "type",
fileData: "CARD",
databaseData: filterdLicenses[0].type,
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
if (filterdLicenses[0].status !== "Unallocated") {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "status",
fileData: "Unallocated",
databaseData: filterdLicenses[0].status,
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
if (filterdLicenses[0].allocated_user_id !== null) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "allocated_user_id",
fileData: null,
databaseData: filterdLicenses[0].allocated_user_id.toString(),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
if (filterdLicenses[0].order_id !== null) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "order_id",
fileData: null,
databaseData: filterdLicenses[0].order_id.toString(),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
if (filterdLicenses[0].deleted_at !== null) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "deleted_at",
fileData: null,
databaseData: getFormattedDate(
filterdLicenses[0].deleted_at,
`yyyy/MM/dd hh:mm:ss`
),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
if (filterdLicenses[0].delete_order_id !== null) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "delete_order_id",
fileData: null,
databaseData: filterdLicenses[0].delete_order_id.toString(),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
row = row + 1;
}
return isNoError;
}
// ライセンス情報の突き合わせを行い、エラー時はエラー情報配列に情報追加する
function compareLicenses(
VerificationResultDetails: VerificationResultDetails[],
licensesFromFile: csvInputFileWithRow[],
licensesFromDatabase: License[],
accountsMappingInputFiles: AccountsMappingFile[]
): boolean {
let isNoError = true;
for (let i = 0; i < licensesFromFile.length; i++) {
if (
!licensesFromDatabase[i] ||
licensesFromFile[i].account_id !==
findAccountIdText(
accountsMappingInputFiles,
licensesFromDatabase[i].account_id
)
) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "Account_transition",
inputRow: licensesFromFile[i].row,
diffTargetTable: "licenses",
columnName: "account_id",
fileData: licensesFromFile[i].account_id,
databaseData: licensesFromDatabase[i]
? findAccountIdText(
accountsMappingInputFiles,
licensesFromDatabase[i].account_id
) + `(${licensesFromDatabase[i].account_id})`
: "undifined",
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
// expiry_dateについて、時はゼロパディングした値で比較する×0109 ○19
if (
!licensesFromDatabase[i] ||
licensesFromFile[i].expired_date !==
getFormattedDate(
licensesFromDatabase[i].expiry_date,
`yyyy/MM/dd hh:mm:ss`,
true
)
) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "Account_transition",
inputRow: licensesFromFile[i].row,
diffTargetTable: "licenses",
columnName: "expired_date",
fileData: licensesFromFile[i].expired_date,
databaseData: licensesFromDatabase[i]
? getFormattedDate(
licensesFromDatabase[i].expiry_date,
`yyyy/MM/dd hh:mm:ss`,
true
)
: "undifined",
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
}
return isNoError;
}
// アカウント情報の突き合わせを行い、エラー時はエラー情報配列に情報追加する
function compareAccounts(
VerificationResultDetails: VerificationResultDetails[],
accountsFromFile: csvInputFileWithRow[],
countriesFromFile: csvInputFileWithRow[],
accountsFromDatabase: Account[],
accountsMappingInputFiles: AccountsMappingFile[]
): boolean {
let isNoError = true;
for (const accountFromFile of accountsFromFile) {
// DBレコードの存在チェック
const filterdAccounts = accountsFromDatabase.filter(
(accountsFromDatabase) =>
findAccountIdText(
accountsMappingInputFiles,
accountsFromDatabase.id
) === accountFromFile.account_id
);
if (filterdAccounts.length === 0) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "Account_transition",
inputRow: accountFromFile.row,
diffTargetTable: "accounts",
columnName: "account_id",
fileData: accountFromFile.account_id,
databaseData: "-",
reason: "レコード無し",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
continue;
}
// 項目チェックparent_account_id
const transratedParentId = transrateCountryHierarchy(
countriesFromFile,
accountFromFile.parent_id
);
if (
transratedParentId !==
findAccountIdText(
accountsMappingInputFiles,
filterdAccounts[0].parent_account_id
)
) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "Account_transition",
inputRow: accountFromFile.row,
diffTargetTable: "accounts",
columnName: "parent_account_id",
fileData:
transratedParentId === accountFromFile.parent_id
? accountFromFile.parent_id
: `${transratedParentId}(${accountFromFile.parent_id})`,
databaseData:
findAccountIdText(
accountsMappingInputFiles,
filterdAccounts[0].parent_account_id
) + `(${filterdAccounts[0].parent_account_id})`,
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
continue;
}
// 項目チェックtier
if (
accountFromFile.type !== getMigrationTypeByNumber(filterdAccounts[0].tier)
) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "Account_transition",
inputRow: accountFromFile.row,
diffTargetTable: "accounts",
columnName: "tier",
fileData: accountFromFile.type,
databaseData: getMigrationTypeByNumber(filterdAccounts[0].tier),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
continue;
}
// 項目チェックcountry
if (
accountFromFile.country !==
getCountryLabelByValue(filterdAccounts[0].country)
) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "Account_transition",
inputRow: accountFromFile.row,
diffTargetTable: "accounts",
columnName: "country",
fileData: accountFromFile.country,
databaseData: getCountryLabelByValue(filterdAccounts[0].country),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
continue;
}
// 項目チェックcompany_name
if (accountFromFile.company_name !== filterdAccounts[0].company_name) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "Account_transition",
inputRow: accountFromFile.row,
diffTargetTable: "accounts",
columnName: "company_name",
fileData: accountFromFile.company_name,
databaseData: filterdAccounts[0].company_name,
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
continue;
}
}
return isNoError;
}

View File

@ -64,41 +64,57 @@ export class AdB2cService {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.createUser.name}`
);
try {
// ユーザをADB2Cに登録
const newUser = await this.graphClient.api("users/").post({
accountEnabled: true,
displayName: username,
passwordPolicies: "DisableStrongPassword",
passwordProfile: {
forceChangePasswordNextSignIn: false,
password: password,
},
identities: [
{
signinType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: `${this.tenantName}.onmicrosoft.com`,
issuerAssignedId: email,
const retryCount: number = 3;
let retry = 0;
while (retry < retryCount) {
try {
// ユーザをADB2Cに登録
const newUser = await this.graphClient.api("users/").post({
accountEnabled: true,
displayName: username,
passwordPolicies: "DisableStrongPassword",
passwordProfile: {
forceChangePasswordNextSignIn: false,
password: password,
},
],
});
return { sub: newUser.id };
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e?.statusCode === 400 && e?.body) {
const error = JSON.parse(e.body);
identities: [
{
signinType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: `${this.tenantName}.onmicrosoft.com`,
issuerAssignedId: email,
},
],
});
this.logger.log(
`[${context.getTrackingId()}] [ADB2C CREATE] newUser: ${newUser}`
);
return { sub: newUser.id };
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e?.statusCode === 400 && e?.body) {
const error = JSON.parse(e.body);
// エラーが競合エラーである場合は、メールアドレス重複としてエラーを返す
if (error?.details?.find((x) => x.code === "ObjectConflict")) {
return { reason: "email", message: "ObjectConflict" };
// エラーが競合エラーである場合は、メールアドレス重複としてエラーを返す
if (error?.details?.find((x) => x.code === "ObjectConflict")) {
return { reason: "email", message: "ObjectConflict" };
}
}
}
throw e;
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.createUser.name}`
);
if (++retry < retryCount) {
this.logger.log(`ADB2Cエラー発生。5秒sleepしてリトライします (${retry}/${retryCount})...`);
await new Promise(resolve => setTimeout(resolve, 5000));
} else {
this.logger.log(`リトライ数が上限に達したのでエラーを返却します`);
throw e;
}
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.createUser.name}`
);
}
}
}

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;
@ -160,4 +163,21 @@ export class AccountsRepositoryService {
);
});
}
/**
*
* @returns Account[]
*/
async getAllAccounts(
context: Context,
): Promise<Account[]> {
return await this.dataSource.transaction(async (entityManager) => {
const accountsRepo = entityManager.getRepository(Account);
const accouts = accountsRepo.find({
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
});
return accouts;
});
}
}

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);
}
@ -172,4 +166,34 @@ export class LicensesRepositoryService {
return {};
});
}
/**
*
* @returns License[]
*/
async getAllLicenses(context: Context): Promise<License[]> {
return await this.dataSource.transaction(async (entityManager) => {
const licenseRepo = entityManager.getRepository(License);
const licenses = licenseRepo.find({
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
});
return licenses;
});
}
/**
*
* @returns CardLicense[]
*/
async getAllCardLicense(context: Context): Promise<CardLicense[]> {
return await this.dataSource.transaction(async (entityManager) => {
const cardLicenseRepo = entityManager.getRepository(CardLicense);
const cardLicenses = cardLicenseRepo.find({
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
});
return cardLicenses;
});
}
}

View File

@ -138,4 +138,20 @@ export class UsersRepositoryService {
await deleteEntity(usersRepo, { id: userId }, this.isCommentOut, context);
});
}
/**
*
* @returns User[]
*/
async getAllUsers(context: Context): Promise<User[]> {
return await this.dataSource.transaction(async (entityManager) => {
const userRepo = entityManager.getRepository(User);
const users = userRepo.find({
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
});
return users;
});
}
}

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

View File

@ -15,9 +15,12 @@ export const makePassword = (): string => {
// autoGeneratedPasswordが以上の条件を満たせばvalidがtrueになる
let valid = false;
let autoGeneratedPassword: string = '';
let autoGeneratedPassword = '';
while (!valid) {
// 再生成用に変数を初期化する
autoGeneratedPassword = '';
// パスワードをランダムに決定
while (autoGeneratedPassword.length < passLength) {
// 上で決定したcharsの中からランダムに1文字ずつ追加

View File

@ -950,6 +950,72 @@ describe('TasksService', () => {
expect(task.jobNumber).toEqual('00000001');
}
});
it('[Admin] Taskが100件であっても取得できる', async () => {
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
if (!source) fail();
const module = await makeTaskTestingModuleWithNotificaiton(
source,
notificationhubServiceMockValue,
);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id } = await makeTestUser(source, {
account_id: accountId,
external_id: 'userId',
role: 'none',
});
const { id: authorUserId, author_id } = await makeTestUser(source, {
account_id: accountId,
external_id: 'userId',
author_id: 'MY_AUTHOR_ID',
role: 'author',
});
const service = module.get<TasksService>(TasksService);
for (let i = 0; i < 100; i++) {
await createTask(
source,
accountId,
authorUserId,
author_id ?? '',
`WORKTYPE${i + 1}`,
'01',
// 00000001 ~ 00000100
`000000${String(i + 1).padStart(2, '0')}`,
'Uploaded',
);
}
const offset = 0;
const limit = 100;
const status = ['Uploaded', 'Backup'];
const paramName = 'WORK_TYPE';
const direction = 'DESC';
const { tasks, total } = await service.getTasks(
makeContext('trackingId', 'requestId'),
external_id,
[ADMIN_ROLES.ADMIN, USER_ROLES.NONE],
offset,
limit,
status,
paramName,
direction,
);
expect(tasks.length).toEqual(100);
expect(total).toEqual(100);
// ソート条件がWORK_TYPEのため、WORK_TYPEが降順になっていることを確認
expect(tasks[0].workType).toEqual('WORKTYPE99');
expect(tasks[99].workType).toEqual('WORKTYPE1');
expect(tasks[0].optionItemList).toEqual(
Array.from({ length: 10 }).map((_, i) => {
return {
optionItemLabel: `label${i}:audio_file_id${tasks[0].audioFileId}`,
optionItemValue: `value${i}:audio_file_id${tasks[0].audioFileId}`,
};
}),
);
});
});
});

View File

@ -438,12 +438,14 @@ export class TasksService {
`author_id not found. audioFileId: ${audioFileId}. account_id: ${user.account_id}`,
);
}
const { external_id: authorExternalId } =
await this.usersRepository.findUserByAuthorId(
context,
task.file.author_id,
user.account_id,
);
const {
external_id: authorExternalId,
notification: authorNotification,
} = await this.usersRepository.findUserByAuthorId(
context,
task.file.author_id,
user.account_id,
);
// プライマリ管理者を取得
const { external_id: primaryAdminExternalId } =
@ -457,6 +459,7 @@ export class TasksService {
]);
// メール送信に必要な情報を取得
// Author通知ON/OFF関わらずAuthor名は必要なため、情報の取得は行う
const author = usersInfo.find((x) => x.id === authorExternalId);
if (!author) {
throw new Error(`author not found. id=${authorExternalId}`);
@ -491,7 +494,7 @@ export class TasksService {
// メール送信
this.sendgridService.sendMailWithU117(
context,
authorEmail,
authorNotification ? authorEmail : null,
typistEmail,
authorName,
task.file.file_name.replace('.zip', ''),

View File

@ -79,12 +79,18 @@ const createTask = (
const createAudioOptionItems = (
optionItems: AudioOptionItemEntity[],
): AudioOptionItem[] => {
return optionItems.map((x) => {
return {
optionItemLabel: x.label,
optionItemValue: x.value,
};
});
// バグ 3786: [FB対応]タスク一覧画面のOptionItemがソート条件によって表示順がおかしくなる の対応
// 並び順をID順に固定する
// 本来はRepository側でソートするべきだが、TYPEORMの仕様でソートすると取得件数が想定通りに取得できないため、ここでソートする
// 詳細は タスク 3815: タスク一覧画面の取得件数が10件となっているバグの対応
return optionItems
.sort((a: AudioOptionItemEntity, b: AudioOptionItemEntity) => a.id - b.id)
.map((x) => {
return {
optionItemLabel: x.label,
optionItemValue: x.value,
};
});
};
// Repository側のDTOからAssigneeオブジェクトを構築する

View File

@ -1051,7 +1051,7 @@ export class SendGridService {
*/
async sendMailWithU117(
context: Context,
authorEmail: string,
authorEmail: string | null,
typistEmail: string,
authorName: string,
fileName: string,
@ -1079,7 +1079,7 @@ export class SendGridService {
// メールを送信する
await this.sendMail(
context,
[authorEmail, typistEmail],
[authorEmail, typistEmail].filter((x): x is string => x !== null), // authorEmailがnullの場合は除外する
[],
this.mailFrom,
subject,

View File

@ -1582,91 +1582,78 @@ const makeOrder = (
priority: 'DESC',
job_number: direction,
id: 'ASC',
option_items: { id: 'ASC' },
};
case 'STATUS':
return {
priority: 'DESC',
status: direction,
id: 'ASC',
option_items: { id: 'ASC' },
};
case 'TRANSCRIPTION_FINISHED_DATE':
return {
priority: 'DESC',
finished_at: direction,
id: 'ASC',
option_items: { id: 'ASC' },
};
case 'TRANSCRIPTION_STARTED_DATE':
return {
priority: 'DESC',
started_at: direction,
id: 'ASC',
option_items: { id: 'ASC' },
};
case 'AUTHOR_ID':
return {
priority: 'DESC',
file: { author_id: direction },
id: 'ASC',
option_items: { id: 'ASC' },
};
case 'ENCRYPTION':
return {
priority: 'DESC',
file: { is_encrypted: direction },
id: 'ASC',
option_items: { id: 'ASC' },
};
case 'FILE_LENGTH':
return {
priority: 'DESC',
file: { duration: direction },
id: 'ASC',
option_items: { id: 'ASC' },
};
case 'FILE_NAME':
return {
priority: 'DESC',
file: { file_name: direction },
id: 'ASC',
option_items: { id: 'ASC' },
};
case 'FILE_SIZE':
return {
priority: 'DESC',
file: { file_size: direction },
id: 'ASC',
option_items: { id: 'ASC' },
};
case 'RECORDING_FINISHED_DATE':
return {
priority: 'DESC',
file: { finished_at: direction },
id: 'ASC',
option_items: { id: 'ASC' },
};
case 'RECORDING_STARTED_DATE':
return {
priority: 'DESC',
file: { started_at: direction },
id: 'ASC',
option_items: { id: 'ASC' },
};
case 'UPLOAD_DATE':
return {
priority: 'DESC',
file: { uploaded_at: direction },
id: 'ASC',
option_items: { id: 'ASC' },
};
case 'WORK_TYPE':
return {
priority: 'DESC',
file: { work_type_id: direction },
id: 'ASC',
option_items: { id: 'ASC' },
};
default:
// switchのcase漏れが発生した場合に型エラーになるようにする