makabe.t b8b3416795 Merged PR 625: セレクトのクエリに追跡用のIDと実行日時の情報を追加する
## 概要
[Task3288: セレクトのクエリに追跡用のIDと実行日時の情報を追加する](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3288)

- リポジトリ内でのDB操作でSelect文となる部分にコメント(追跡ID_日時)を追加しました。
  - `find`, `fineOne`, `count`を対象にしています。
- コメントを追加するにあたってContextをリポジトリメソッドの引数に追加しています。

## レビューポイント
- 対応箇所の漏れはないでしょうか?
- コメントのつけ方は適切でしょうか?

## UIの変更
- なし

## 動作確認状況
- ローカルで確認
2023-12-13 00:00:15 +00:00

2248 lines
67 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

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

import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { SendGridService } from '../../gateways/sendgrid/sendgrid.service';
import { UsersRepositoryService } from '../../repositories/users/users.repository.service';
import { AccountsRepositoryService } from '../../repositories/accounts/accounts.repository.service';
import {
AdB2cService,
ConflictError,
isConflictError,
} from '../../gateways/adb2c/adb2c.service';
import { Account } from '../../repositories/accounts/entity/account.entity';
import { User } from '../../repositories/users/entity/user.entity';
import {
TIERS,
USER_ROLES,
ADB2C_SIGN_IN_TYPE,
OPTION_ITEM_VALUE_TYPE,
MANUAL_RECOVERY_REQUIRED,
} from '../../constants';
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
import {
TypistGroup,
GetPartnerLicensesResponse,
PartnerLicenseInfo,
GetOrderHistoriesResponse,
LicenseOrder,
GetDealersResponse,
Dealer,
GetMyAccountResponse,
GetTypistGroupResponse,
GetWorktypesResponse,
GetOptionItemsResponse,
GetPartnersResponse,
PostWorktypeOptionItem,
Author,
Partner,
GetCompanyNameResponse,
} from './types/types';
import {
DateWithZeroTime,
ExpirationThresholdDate,
} from '../licenses/types/types';
import { GetLicenseSummaryResponse, Typist } from './types/types';
import { UserNotFoundError } from '../../repositories/users/errors/types';
import { UserGroupsRepositoryService } from '../../repositories/user_groups/user_groups.repository.service';
import { makePassword } from '../../common/password';
import { LicensesRepositoryService } from '../../repositories/licenses/licenses.repository.service';
import {
AccountNotFoundError,
AdminUserNotFoundError,
DealerAccountNotFoundError,
} from '../../repositories/accounts/errors/types';
import { Context } from '../../common/log';
import {
LicensesShortageError,
AlreadyIssuedError,
OrderNotFoundError,
AlreadyLicenseStatusChangedError,
AlreadyLicenseAllocatedError,
CancellationPeriodExpiredError,
} from '../../repositories/licenses/errors/types';
import { BlobstorageService } from '../../gateways/blobstorage/blobstorage.service';
import {
TypistGroupNotExistError,
TypistIdInvalidError,
} from '../../repositories/user_groups/errors/types';
import { WorktypesRepositoryService } from '../../repositories/worktypes/worktypes.repository.service';
import {
WorktypeIdAlreadyExistsError,
WorktypeIdInUseError,
WorktypeIdMaxCountError,
WorktypeIdNotFoundError,
} from '../../repositories/worktypes/errors/types';
@Injectable()
export class AccountsService {
private readonly mailFrom =
this.configService.getOrThrow<string>('MAIL_FROM');
constructor(
private readonly accountRepository: AccountsRepositoryService,
private readonly licensesRepository: LicensesRepositoryService,
private readonly usersRepository: UsersRepositoryService,
private readonly userGroupsRepository: UserGroupsRepositoryService,
private readonly worktypesRepository: WorktypesRepositoryService,
private readonly adB2cService: AdB2cService,
private readonly sendgridService: SendGridService,
private readonly blobStorageService: BlobstorageService,
private readonly configService: ConfigService,
) {}
private readonly logger = new Logger(AccountsService.name);
/**
* 第五階層用のライセンス情報を取得する
* @param accountId
* @returns LicenseSummary
*/
async getLicenseSummary(
context: Context,
accountId: number,
): Promise<GetLicenseSummaryResponse> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getLicenseSummary.name
} | params: { ` + `accountId: ${accountId}, };`,
);
try {
const currentDate = new DateWithZeroTime();
const expiringSoonDate = new ExpirationThresholdDate(
currentDate.getTime(),
);
const { licenseSummary, isStorageAvailable } =
await this.accountRepository.getLicenseSummaryInfo(
context,
accountId,
currentDate,
expiringSoonDate,
);
const {
allocatableLicenseWithMargin,
expiringSoonLicense,
totalLicense,
allocatedLicense,
reusableLicense,
freeLicense,
issueRequesting,
numberOfRequesting,
} = licenseSummary;
let shortage = allocatableLicenseWithMargin - expiringSoonLicense;
shortage = shortage >= 0 ? 0 : Math.abs(shortage);
const licenseSummaryResponse: GetLicenseSummaryResponse = {
totalLicense,
allocatedLicense,
reusableLicense,
freeLicense,
expiringWithin14daysLicense: expiringSoonLicense,
issueRequesting,
numberOfRequesting,
storageSize: 0, // XXX PBI1201対象外
usedSize: 0, // XXX PBI1201対象外
shortage,
isStorageAvailable,
};
return licenseSummaryResponse;
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(
`[${context.getTrackingId()}] get licenseSummary failed`,
);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getLicenseSummary.name}`,
);
}
}
/**
* アカウント情報をDBに作成する
* @param companyName
* @param country
* @param [dealerAccountId]
* @returns account
*/
async createAccount(
context: Context,
companyName: string,
country: string,
dealerAccountId: number | undefined,
email: string,
password: string,
username: string,
role: string,
acceptedEulaVersion: string,
acceptedPrivacyNoticeVersion: string,
acceptedDpaVersion: string,
): Promise<{ accountId: number; userId: number; externalUserId: string }> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.createAccount.name
} | params: { ` +
`dealerAccountId: ${dealerAccountId}, ` +
`role: ${role}, ` +
`acceptedEulaVersion: ${acceptedEulaVersion}, ` +
`acceptedPrivacyNoticeVersion: ${acceptedPrivacyNoticeVersion}, ` +
`acceptedDpaVersion: ${acceptedDpaVersion} };`,
);
try {
let externalUser: { sub: string } | ConflictError;
try {
// idpにユーザーを作成
externalUser = await this.adB2cService.createUser(
context,
email,
password,
username,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(
`[${context.getTrackingId()}] create externalUser failed`,
);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
// メールアドレス重複エラー
if (isConflictError(externalUser)) {
this.logger.error(
`[${context.getTrackingId()}] email conflict. externalUser: ${externalUser}`,
);
throw new HttpException(
makeErrorResponse('E010301'),
HttpStatus.BAD_REQUEST,
);
}
let account: Account;
let user: User;
try {
// アカウントと管理者をセットで作成
const { newAccount, adminUser } =
await this.accountRepository.createAccount(
companyName,
country,
dealerAccountId,
TIERS.TIER5,
externalUser.sub,
role,
acceptedEulaVersion,
acceptedPrivacyNoticeVersion,
acceptedDpaVersion,
);
account = newAccount;
user = adminUser;
this.logger.log(
`[${context.getTrackingId()}] adminUser.external_id: ${
user.external_id
}`,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(`[${context.getTrackingId()}] create account failed`);
//リカバリ処理
// idpのユーザーを削除
await this.deleteAdB2cUser(externalUser.sub, context);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
// 新規作成アカウント用のBlobコンテナを作成
try {
await this.blobStorageService.createContainer(
context,
account.id,
country,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(
`[${context.getTrackingId()}] create container failed`,
);
//リカバリ処理
// idpのユーザーを削除
await this.deleteAdB2cUser(externalUser.sub, context);
// DBのアカウントを削除
await this.deleteAccount(account.id, user.id, context);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
try {
// メールの内容を構成
const { subject, text, html } =
await this.sendgridService.createMailContentFromEmailConfirm(
context,
account.id,
user.id,
email,
);
// メールを送信
await this.sendgridService.sendMail(
context,
email,
this.mailFrom,
subject,
text,
html,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(`[${context.getTrackingId()}] send E-mail failed`);
//リカバリ処理
// idpのユーザーを削除
await this.deleteAdB2cUser(externalUser.sub, context);
// DBのアカウントを削除
await this.deleteAccount(account.id, user.id, context);
// Blobコンテナを削除
await this.deleteBlobContainer(account.id, country, context);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
return {
accountId: account.id,
userId: user.id,
externalUserId: user.external_id,
};
} catch (e) {
throw e;
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.createAccount.name}`,
);
}
}
// AdB2cのユーザーを削除
// TODO「タスク 2452: リトライ処理を入れる箇所を検討し、実装する」の候補
private async deleteAdB2cUser(
externalUserId: string,
context: Context,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.createAccount.name
} | params: { ` + `externalUserId: ${externalUserId}};`,
);
try {
await this.adB2cService.deleteUser(externalUserId, context);
this.logger.log(
`[${context.getTrackingId()}] delete externalUser: ${externalUserId} | params: { ` +
`externalUserId: ${externalUserId}, };`,
);
} catch (error) {
this.logger.error(`[${context.getTrackingId()}] error=${error}`);
this.logger.error(
`${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete externalUser: ${externalUserId}`,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.deleteAdB2cUser.name}`,
);
}
}
// DBのアカウントを削除
private async deleteAccount(
accountId: number,
userId: number,
context: Context,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.deleteAccount.name
} | params: { accountId: ${accountId}, userId: ${userId} };`,
);
try {
await this.accountRepository.deleteAccount(accountId, userId);
this.logger.log(
`[${context.getTrackingId()}] delete account: ${accountId}, user: ${userId}`,
);
} catch (error) {
this.logger.error(`[${context.getTrackingId()}] error=${error}`);
this.logger.error(
`${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete account: ${accountId}, user: ${userId}`,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.deleteAccount.name}`,
);
}
}
// Blobコンテナを削除
// TODO「タスク 2452: リトライ処理を入れる箇所を検討し、実装する」の候補
private async deleteBlobContainer(
accountId: number,
country: string,
context: Context,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.deleteBlobContainer.name
} | params: { accountId: ${accountId} };`,
);
try {
await this.blobStorageService.deleteContainer(
context,
accountId,
country,
);
this.logger.log(
`[${context.getTrackingId()}] delete container: ${accountId}, country: ${country}`,
);
} catch (error) {
this.logger.error(
`${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete container: ${accountId}, country: ${country}`,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.deleteBlobContainer.name}`,
);
}
}
/**
* パラメータのユーザIDからアカウント情報を取得する
* @param externalId
* @returns GetMyAccountResponse
*/
async getAccountInfo(
context: Context,
externalId: string,
): Promise<GetMyAccountResponse> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getAccountInfo.name
} | params: { ` + `externalId: ${externalId}, };`,
);
try {
let userInfo: User;
userInfo = await this.usersRepository.findUserByExternalId(
context,
externalId,
);
let accountInfo: Account;
accountInfo = await this.accountRepository.findAccountById(
context,
userInfo.account_id,
);
let parentInfo: Account | undefined;
if (accountInfo.parent_account_id) {
parentInfo = await this.accountRepository.findAccountById(
context,
accountInfo.parent_account_id,
);
}
return {
account: {
accountId: userInfo.account_id,
companyName: accountInfo.company_name,
tier: accountInfo.tier,
country: accountInfo.country,
parentAccountId: accountInfo.parent_account_id ?? undefined,
delegationPermission: accountInfo.delegation_permission,
primaryAdminUserId: accountInfo.primary_admin_user_id ?? undefined,
secondryAdminUserId: accountInfo.secondary_admin_user_id ?? undefined,
parentAccountName: parentInfo ? parentInfo.company_name : undefined,
},
};
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
switch (e.constructor) {
case UserNotFoundError:
throw new HttpException(
makeErrorResponse('E010204'),
HttpStatus.BAD_REQUEST,
);
case AccountNotFoundError:
throw new HttpException(
makeErrorResponse('E010501'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getAccountInfo.name}`,
);
}
}
async getTypistGroups(
context: Context,
externalId: string,
): Promise<TypistGroup[]> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getTypistGroups.name
} | params: { externalId: ${externalId} };`,
);
// TypistGroup取得
try {
const user = await this.usersRepository.findUserByExternalId(
context,
externalId,
);
const userGroups = await this.userGroupsRepository.getUserGroups(
context,
user.account_id,
);
return userGroups.map((x) => ({ id: x.id, name: x.name }));
} 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.getTypistGroups.name}`,
);
}
}
/**
* IDを指定してタイピストグループを取得する
* @param context
* @param externalId
* @param typistGroupId
* @returns typist group
*/
async getTypistGroup(
context: Context,
externalId: string,
typistGroupId: number,
): Promise<GetTypistGroupResponse> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getTypistGroup.name
} | params: { externalId: ${externalId}, typistGroupId: ${typistGroupId} };`,
);
try {
const { account_id } = await this.usersRepository.findUserByExternalId(
context,
externalId,
);
const { name, userGroupMembers } =
await this.userGroupsRepository.getTypistGroup(
context,
account_id,
typistGroupId,
);
if (!userGroupMembers) {
throw new TypistGroupNotExistError(
`Typist Group is not exist. typistGroupId: ${typistGroupId}`,
);
}
return {
typistGroupName: name,
typistIds: userGroupMembers.map((x) => x.user_id),
};
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case TypistGroupNotExistError:
throw new HttpException(
makeErrorResponse('E010908'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getTypistGroup.name}`,
);
}
}
/**
* Gets typists
* @param externalId
* @returns typists
*/
async getTypists(context: Context, externalId: string): Promise<Typist[]> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getTypists.name
} | params: { externalId: ${externalId} };`,
);
// Typist取得
try {
const typistUsers = await this.usersRepository.findTypistUsers(
context,
externalId,
);
const externalIds = typistUsers.map((x) => x.external_id);
// B2Cからユーザー名を取得する
const adb2cUsers = await this.adB2cService.getUsers(context, externalIds);
const typists = typistUsers.map((x) => {
const user = adb2cUsers.find((adb2c) => adb2c.id === x.external_id);
if (!user) {
throw new Error(
`user not found. externalId: ${x.external_id}, userId: ${x.id}`,
);
}
return {
id: x.id,
name: user.displayName,
};
});
return typists;
} 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.getTypists.name}`,
);
}
}
/**
* アカウント内のAuthorを取得する
* @param context
* @param externalId
* @returns authors
*/
async getAuthors(context: Context, externalId: string): Promise<Author[]> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getAuthors.name
} | params: { externalId: ${externalId} };`,
);
try {
const { account } = await this.usersRepository.findUserByExternalId(
context,
externalId,
);
if (!account) {
throw new AccountNotFoundError(
`account not found. externalId: ${externalId}`,
);
}
const authorUsers = await this.usersRepository.findAuthorUsers(
context,
account.id,
);
const authors = authorUsers.map((x) => {
if (!x.author_id) {
throw new Error(
`author_id is Not Found. externalId: ${x.external_id}, userId: ${x.id}`,
);
}
return {
id: x.id,
authorId: x.author_id,
};
});
return authors;
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case AccountNotFoundError:
throw new HttpException(
makeErrorResponse('E010501'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getAuthors.name}`,
);
}
}
/**
* パートナーを追加する
* @param companyName パートナーの会社名
* @param country パートナーの所属する国
* @param email パートナーの管理者のメールアドレス
* @param adminName パートナーの管理者の名前
* @param creatorUserId パートナーを作成する上位階層アカウントのユーザーID
* @param creatorAccountTier パートナーを作成する上位階層アカウントの階層
*/
async createPartnerAccount(
context: Context,
companyName: string,
country: string,
email: string,
adminName: string,
creatorUserId: string,
creatorAccountTier: number,
): Promise<{ accountId: number }> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.createPartnerAccount.name
} | params: { creatorUserId: ${creatorUserId}, creatorAccountTier: ${creatorAccountTier} };`,
);
try {
let myAccountId: number;
try {
// アクセストークンからユーザーIDを取得する
myAccountId = (
await this.usersRepository.findUserByExternalId(
context,
creatorUserId,
)
).account_id;
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof UserNotFoundError) {
throw new HttpException(
makeErrorResponse('E010204'),
HttpStatus.BAD_REQUEST,
);
} else {
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
const ramdomPassword = makePassword();
let externalUser: { sub: string } | ConflictError;
try {
// 管理者ユーザを作成し、AzureADB2C IDを取得する
externalUser = await this.adB2cService.createUser(
context,
email,
ramdomPassword,
adminName,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
// メールアドレスが重複していた場合はエラーを返す
if (isConflictError(externalUser)) {
throw new HttpException(
makeErrorResponse('E010301'),
HttpStatus.BAD_REQUEST,
);
}
let account: Account;
let user: User;
try {
// アカウントと管理者をセットで作成
const { newAccount, adminUser } =
await this.accountRepository.createAccount(
companyName,
country,
myAccountId,
creatorAccountTier + 1,
externalUser.sub,
USER_ROLES.NONE,
undefined,
undefined,
);
account = newAccount;
user = adminUser;
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(
`[${context.getTrackingId()}] create partner account failed`,
);
//リカバリ処理
// idpのユーザーを削除
await this.deleteAdB2cUser(externalUser.sub, context);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
try {
// 新規作成アカウント用のBlobコンテナを作成
await this.blobStorageService.createContainer(
context,
account.id,
country,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(
`[${context.getTrackingId()}] create partner container failed`,
);
//リカバリ処理
// idpのユーザーを削除
await this.deleteAdB2cUser(externalUser.sub, context);
// DBのアカウントとユーザーを削除
await this.deleteAccount(account.id, user.id, context);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
try {
const { subject, text, html } =
await this.sendgridService.createMailContentFromEmailConfirmForNormalUser(
context,
account.id,
user.id,
email,
);
await this.sendgridService.sendMail(
context,
email,
this.mailFrom,
subject,
text,
html,
);
return { accountId: account.id };
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(
`[${context.getTrackingId()}] create partner account send mail failed`,
);
//リカバリ処理
// idpのユーザーを削除
await this.deleteAdB2cUser(externalUser.sub, context);
// DBのアカウントを削除
await this.deleteAccount(account.id, user.id, context);
// Blobコンテナを削除
await this.deleteBlobContainer(account.id, country, context);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
} catch (e) {
throw e;
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.createPartnerAccount.name}`,
);
}
}
/**
* パートナーライセンス情報を取得する
* @param limit
* @param offset
* @param accountId
* @returns getPartnerLicensesResponse
*/
async getPartnerLicenses(
context: Context,
limit: number,
offset: number,
accountId: number,
): Promise<GetPartnerLicensesResponse> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getPartnerLicenses.name
} | params: { limit: ${limit}, offset: ${offset}, accountId: ${accountId} };`,
);
try {
const currentDate = new DateWithZeroTime();
// 第五階層のshortage算出に使用する日付情報
// 「有効期限が現在日付からしきい値以内のライセンス数」を取得するため、しきい値となる日付を作成する
const expiringSoonDate = new ExpirationThresholdDate(
currentDate.getTime(),
);
const getPartnerLicenseResult =
await this.accountRepository.getPartnerLicense(
context,
accountId,
currentDate,
expiringSoonDate,
offset,
limit,
);
// 自アカウントのShortageを算出してreturn用の変数にマージする
let ownShortage =
getPartnerLicenseResult.ownPartnerLicenseFromRepository.stockLicense -
getPartnerLicenseResult.ownPartnerLicenseFromRepository.issuedRequested;
// 「不足している値」を取得するため、負数の場合は絶対値とし、0以上の場合は0とする
ownShortage = ownShortage >= 0 ? 0 : Math.abs(ownShortage);
// return用の型にリポジトリから取得した型をマージし、不足項目shortageを設定する
const ownPartnerLicense: PartnerLicenseInfo = Object.assign(
{},
getPartnerLicenseResult.ownPartnerLicenseFromRepository,
{
shortage: ownShortage,
},
);
// 各子アカウントのShortageを算出してreturn用の変数にマージする
const childrenPartnerLicenses: PartnerLicenseInfo[] = [];
for (const childPartnerLicenseFromRepository of getPartnerLicenseResult.childPartnerLicensesFromRepository) {
const { allocatableLicenseWithMargin, expiringSoonLicense } =
childPartnerLicenseFromRepository;
let childShortage: number = 0;
if (childPartnerLicenseFromRepository.tier === TIERS.TIER5) {
if (
allocatableLicenseWithMargin === undefined ||
expiringSoonLicense === undefined
) {
throw new Error(
`Tier5 account has no allocatableLicenseWithMargin or expiringSoonLicense. accountId: ${accountId}`,
);
}
childShortage = allocatableLicenseWithMargin - expiringSoonLicense;
} else {
childShortage =
childPartnerLicenseFromRepository.stockLicense -
childPartnerLicenseFromRepository.issuedRequested;
}
// 「不足している値」を取得するため、負数の場合は絶対値とし、0以上の場合は0とする
childShortage = childShortage >= 0 ? 0 : Math.abs(childShortage);
const childPartnerLicense: PartnerLicenseInfo = Object.assign(
{},
childPartnerLicenseFromRepository,
{
shortage: childShortage,
},
);
childrenPartnerLicenses.push(childPartnerLicense);
}
const getPartnerLicensesResponse: GetPartnerLicensesResponse = {
total: getPartnerLicenseResult.total,
ownPartnerLicense: ownPartnerLicense,
childrenPartnerLicenses: childrenPartnerLicenses,
};
return getPartnerLicensesResponse;
} 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.getPartnerLicenses.name}`,
);
}
}
/**
* 注文履歴情報を取得する
* @param limit
* @param offset
* @param accountId
* @returns getOrderHistoriesResponse
*/
async getOrderHistories(
context: Context,
limit: number,
offset: number,
accountId: number,
): Promise<GetOrderHistoriesResponse> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getOrderHistories.name
} | params: { limit: ${limit}, offset: ${offset}, accountId: ${accountId} };`,
);
try {
const licenseHistoryInfo =
await this.licensesRepository.getLicenseOrderHistoryInfo(
context,
accountId,
offset,
limit,
);
// 戻り値用に配列の詰めなおしを行う
const orderHistories: LicenseOrder[] = [];
for (const licenseOrder of licenseHistoryInfo.licenseOrders) {
const returnLicenseOrder: LicenseOrder = {
issueDate:
licenseOrder.issued_at !== null
? new Date(licenseOrder.issued_at)
.toISOString()
.substring(0, 10)
.replace(/-/g, '/')
: undefined,
numberOfOrder: licenseOrder.quantity,
orderDate: new Date(licenseOrder.ordered_at)
.toISOString()
.substring(0, 10)
.replace(/-/g, '/'),
poNumber: licenseOrder.po_number,
status: licenseOrder.status,
};
orderHistories.push(returnLicenseOrder);
}
const getOrderHistoriesResponse: GetOrderHistoriesResponse = {
total: licenseHistoryInfo.total,
orderHistories: orderHistories,
};
return getOrderHistoriesResponse;
} 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.getOrderHistories.name}`,
);
}
}
/**
* 対象の注文を発行する
* @param context
* @param orderedAccountId
* @param userId
* @param tier
* @param poNumber
*/
async issueLicense(
context: Context,
orderedAccountId: number,
userId: string,
tier: number,
poNumber: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.issueLicense.name
} | params: { ` +
`orderedAccountId: ${orderedAccountId}, ` +
`userId: ${userId}, ` +
`tier: ${tier}, ` +
`poNumber: ${poNumber} };`,
);
try {
// アクセストークンからユーザーIDを取得する
const myAccountId = (
await this.usersRepository.findUserByExternalId(context, userId)
).account_id;
await this.licensesRepository.issueLicense(
context,
orderedAccountId,
myAccountId,
tier,
poNumber,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case OrderNotFoundError:
throw new HttpException(
makeErrorResponse('E010801'),
HttpStatus.BAD_REQUEST,
);
case AlreadyIssuedError:
throw new HttpException(
makeErrorResponse('E010803'),
HttpStatus.BAD_REQUEST,
);
case LicensesShortageError:
throw new HttpException(
makeErrorResponse('E010804'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.issueLicense.name}`,
);
}
}
// dealersのアカウント情報を取得する
async getDealers(context: Context): Promise<GetDealersResponse> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.getDealers.name}`,
);
try {
const dealerAccounts = await this.accountRepository.findDealerAccounts(
context,
);
const dealers: GetDealersResponse = {
dealers: dealerAccounts.map((dealerAccount): Dealer => {
return {
id: dealerAccount.id,
name: dealerAccount.company_name,
country: dealerAccount.country,
};
}),
};
return dealers;
} 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.getDealers.name}`,
);
}
}
/**
* タイピストグループを作成する
* @param context
* @param externalId
* @param typistGroupName
* @param typistIds
* @returns createTypistGroupResponse
**/
async createTypistGroup(
context: Context,
externalId: string,
typistGroupName: string,
typistIds: number[],
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.createTypistGroup.name
} | params: { ` +
`externalId: ${externalId}, ` +
`typistGroupName: ${typistGroupName}, ` +
`typistIds: ${typistIds} };`,
);
try {
// 外部IDをもとにユーザー情報を取得する
const { account_id } = await this.usersRepository.findUserByExternalId(
context,
externalId,
);
// API実行ユーザーのアカウントIDでタイピストグループを作成し、タイピストグループとtypistIdsのユーザーを紐付ける
await this.userGroupsRepository.createTypistGroup(
context,
typistGroupName,
typistIds,
account_id,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case TypistIdInvalidError:
throw new HttpException(
makeErrorResponse('E010204'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.createTypistGroup.name}`,
);
}
}
/**
* タイピストグループを更新する
* @param context
* @param externalId
* @param typistGroupId
* @param typistGroupName
* @param typistIds
* @returns typist group
*/
async updateTypistGroup(
context: Context,
externalId: string,
typistGroupId: number,
typistGroupName: string,
typistIds: number[],
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.updateTypistGroup.name
} | params: { ` +
`externalId: ${externalId}, ` +
`typistGroupId: ${typistGroupId}, ` +
`typistGroupName: ${typistGroupName}, ` +
`typistIds: ${typistIds} };`,
);
try {
// 外部IDをもとにユーザー情報を取得する
const { account_id } = await this.usersRepository.findUserByExternalId(
context,
externalId,
);
// タイピストグループと所属するタイピストを更新する
await this.userGroupsRepository.updateTypistGroup(
context,
account_id,
typistGroupId,
typistGroupName,
typistIds,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
// タイピストIDが存在しない場合は400エラーを返す
case TypistIdInvalidError:
throw new HttpException(
makeErrorResponse('E010204'),
HttpStatus.BAD_REQUEST,
);
// タイピストグループIDが存在しない場合は400エラーを返す
case TypistGroupNotExistError:
throw new HttpException(
makeErrorResponse('E010908'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.updateTypistGroup.name}`,
);
}
}
/**
* ライセンス発行をキャンセルする
* @param context
* @param extarnalId
* @param poNumber
* @param orderedAccountId
*/
async cancelIssue(
context: Context,
extarnalId: string,
poNumber: string,
orderedAccountId: number,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.cancelIssue.name
} | params: { ` +
`extarnalId: ${extarnalId}, ` +
`poNumber: ${poNumber}, ` +
`orderedAccountId: ${orderedAccountId}, };`,
);
let myAccountId: number;
try {
// ユーザIDからアカウントIDを取得する
myAccountId = (
await this.usersRepository.findUserByExternalId(context, extarnalId)
).account_id;
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
switch (e.constructor) {
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.cancelIssue.name}`,
);
}
// 注文元アカウントIDの親世代を取得
const parentAccountIds = await this.accountRepository.getHierarchyParents(
context,
orderedAccountId,
);
// 自身が存在しない場合、エラー
if (!parentAccountIds.includes(myAccountId)) {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.cancelIssue.name}`,
);
throw new HttpException(
makeErrorResponse('E000108'),
HttpStatus.UNAUTHORIZED,
);
}
try {
// 発行キャンセル処理
await this.accountRepository.cancelIssue(
context,
orderedAccountId,
poNumber,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
switch (e.constructor) {
case AlreadyLicenseStatusChangedError:
throw new HttpException(
makeErrorResponse('E010809'),
HttpStatus.BAD_REQUEST,
);
case CancellationPeriodExpiredError:
throw new HttpException(
makeErrorResponse('E010810'),
HttpStatus.BAD_REQUEST,
);
case AlreadyLicenseAllocatedError:
throw new HttpException(
makeErrorResponse('E010811'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.cancelIssue.name}`,
);
}
}
/**
* ワークタイプ一覧を取得します
* @param context
* @param externalId
* @returns worktypes
*/
async getWorktypes(
context: Context,
externalId: string,
): Promise<GetWorktypesResponse> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getWorktypes.name
} | params: { externalId: ${externalId} };`,
);
try {
// 外部IDをもとにユーザー情報を取得する
const { account_id: accountId } =
await this.usersRepository.findUserByExternalId(context, externalId);
// ワークタイプ一覧とActiveWorktypeIDを取得する
const { worktypes, active_worktype_id } =
await this.worktypesRepository.getWorktypes(context, accountId);
return {
worktypes: worktypes.map((x) => ({
id: x.id,
worktypeId: x.custom_worktype_id,
description: x.description ?? undefined,
})),
active: active_worktype_id,
};
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case AccountNotFoundError:
throw new HttpException(
makeErrorResponse('E010501'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getWorktypes.name}`,
);
}
}
/**
* ワークタイプを作成します
* @param context
* @param externalId
* @param worktypeId
* @param [description]
* @returns worktype
*/
async createWorktype(
context: Context,
externalId: string,
worktypeId: string,
description?: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.createWorktype.name
} | params: { externalId: ${externalId}, worktypeId: ${worktypeId}, description: ${description} };`,
);
try {
// 外部IDをもとにユーザー情報を取得する
const { account_id: accountId } =
await this.usersRepository.findUserByExternalId(context, externalId);
await this.worktypesRepository.createWorktype(
context,
accountId,
worktypeId,
description,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
// WorktypeIDが既に存在する場合は400エラーを返す
case WorktypeIdAlreadyExistsError:
throw new HttpException(
makeErrorResponse('E011001'),
HttpStatus.BAD_REQUEST,
);
// WorktypeIDが登録上限以上の場合は400エラーを返す
case WorktypeIdMaxCountError:
throw new HttpException(
makeErrorResponse('E011002'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.createWorktype.name}`,
);
}
}
/**
* ワークタイプを更新します
* @param context
* @param externalId
* @param id ワークタイプの内部ID
* @param worktypeId ユーザーが設定するワークタイプ名
* @param [description]
* @returns worktype
*/
async updateWorktype(
context: Context,
externalId: string,
id: number,
worktypeId: string,
description?: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.updateWorktype.name
} | params: { ` +
`externalId: ${externalId}, ` +
`id: ${id}, ` +
`worktypeId: ${worktypeId}, ` +
`description: ${description} };`,
);
try {
// 外部IDをもとにユーザー情報を取得する
const { account_id: accountId } =
await this.usersRepository.findUserByExternalId(context, externalId);
// ワークタイプを更新する
await this.worktypesRepository.updateWorktype(
context,
accountId,
id,
worktypeId,
description,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
// ユーザーが設定したWorktypeIDが既存WorktypeのWorktypeIDと重複する場合は400エラーを返す
case WorktypeIdAlreadyExistsError:
throw new HttpException(
makeErrorResponse('E011001'),
HttpStatus.BAD_REQUEST,
);
// 内部IDで指定されたWorktypeが存在しない場合は400エラーを返す
case WorktypeIdNotFoundError:
throw new HttpException(
makeErrorResponse('E011003'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.updateWorktype.name}`,
);
}
}
/**
* ワークタイプを削除します
* @param context
* @param externalId
* @param id
* @returns worktype
*/
async deleteWorktype(
context: Context,
externalId: string,
id: number,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.deleteWorktype.name
} | params: { ` +
`externalId: ${externalId}, ` +
`id: ${id} };`,
);
try {
// 外部IDをもとにユーザー情報を取得する
const { account, account_id: accountId } =
await this.usersRepository.findUserByExternalId(context, externalId);
if (!account) {
throw new AccountNotFoundError(
`account not found. externalId: ${externalId}`,
);
}
// ワークタイプを削除する
await this.worktypesRepository.deleteWorktype(context, accountId, id);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case UserNotFoundError:
throw new HttpException(
makeErrorResponse('E010204'),
HttpStatus.BAD_REQUEST,
);
case AccountNotFoundError:
throw new HttpException(
makeErrorResponse('E010501'),
HttpStatus.BAD_REQUEST,
);
// 内部IDで指定されたWorktypeが存在しない場合は400エラーを返す
case WorktypeIdNotFoundError:
throw new HttpException(
makeErrorResponse('E011003'),
HttpStatus.BAD_REQUEST,
);
// 内部IDで指定されたWorktypeがWorkflowで使用中の場合は400エラーを返す
case WorktypeIdInUseError:
throw new HttpException(
makeErrorResponse('E011004'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.deleteWorktype.name}`,
);
}
}
/**
* ワークタイプに紐づいたオプションアイテム一覧を取得します
* @param context
* @param externalId
* @param id Worktypeの内部ID
* @returns option items
*/
async getOptionItems(
context: Context,
externalId: string,
id: number,
): Promise<GetOptionItemsResponse> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getOptionItems.name
} | params: { ` +
`externalId: ${externalId}, ` +
`id: ${id} };`,
);
try {
// 外部IDをもとにユーザー情報を取得する
const { account_id: accountId } =
await this.usersRepository.findUserByExternalId(context, externalId);
// オプションアイテム一覧を取得する
const optionItems = await this.worktypesRepository.getOptionItems(
context,
accountId,
id,
);
return {
optionItems: optionItems.map((x) => ({
id: x.id,
itemLabel: x.item_label,
defaultValueType: x.default_value_type,
initialValue: x.initial_value,
})),
};
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
// 内部IDで指定されたWorktypeが存在しない場合は400エラーを返す
case WorktypeIdNotFoundError:
throw new HttpException(
makeErrorResponse('E011003'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getOptionItems.name}`,
);
}
}
/**
* オプションアイテムを更新します
* @param context
* @param externalId
* @param id ワークタイプの内部ID
* @param optionItems
* @returns option items
*/
async updateOptionItems(
context: Context,
externalId: string,
id: number,
optionItems: PostWorktypeOptionItem[],
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.updateOptionItems.name
} | params: { ` +
`externalId: ${externalId}, ` +
`id: ${id}, ` +
`optionItems: ${JSON.stringify(optionItems)} };`,
);
try {
// 外部IDをもとにユーザー情報を取得する
const { account_id: accountId } =
await this.usersRepository.findUserByExternalId(context, externalId);
// オプションアイテムを更新する
await this.worktypesRepository.updateOptionItems(
context,
accountId,
id,
// initialValueはdefaultValueTypeがDEFAULTの場合以外は空文字を設定する
optionItems.map((item) => ({
itemLabel: item.itemLabel,
defaultValueType: item.defaultValueType,
initialValue:
item.defaultValueType === OPTION_ITEM_VALUE_TYPE.DEFAULT
? item.initialValue
: '',
})),
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
// 内部IDで指定されたWorktypeが存在しない場合は400エラーを返す
case WorktypeIdNotFoundError:
throw new HttpException(
makeErrorResponse('E011003'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.updateOptionItems.name}`,
);
}
}
/**
* ActiveWorktypeの更新
* @param context
* @param externalId
* @param id ActiveWorktypeの内部ID
* @returns active worktype
*/
async updateActiveWorktype(
context: Context,
externalId: string,
id: number | undefined,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.updateActiveWorktype.name
} | params: { ` +
`externalId: ${externalId}, ` +
`id: ${id} };`,
);
try {
// 外部IDをもとにユーザー情報を取得する
const { account_id: accountId } =
await this.usersRepository.findUserByExternalId(context, externalId);
// ActiveWorktypeを更新する
await this.accountRepository.updateActiveWorktypeId(
context,
accountId,
id,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
// 内部IDで指定されたWorktypeが存在しない場合は400エラーを返す
case WorktypeIdNotFoundError:
throw new HttpException(
makeErrorResponse('E011003'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.updateActiveWorktype.name}`,
);
}
}
/**
* パートナー一覧を取得します
* @param context
* @param externalId
* @param limit
* @param offset
* @returns GetPartnersResponse
*/
async getPartners(
context: Context,
externalId: string,
limit: number,
offset: number,
): Promise<GetPartnersResponse> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getPartners.name
} | params: { ` +
`externalId: ${externalId}, ` +
`limit: ${limit}, ` +
`offset: ${offset}, };`,
);
try {
const { account_id: accountId } =
await this.usersRepository.findUserByExternalId(context, externalId);
const partnersRecords = await this.accountRepository.getPartners(
context,
accountId,
limit,
offset,
);
// DBから取得したユーザーの外部IDをもとにADB2Cからユーザーを取得する
let externalIds = partnersRecords.partnersInfo.map(
(x) => x.primaryAccountExternalId,
);
externalIds = externalIds.filter((item) => item !== undefined);
const adb2cUsers = await this.adB2cService.getUsers(context, externalIds);
// DBから取得した情報とADB2Cから取得した情報をマージ
const partners = partnersRecords.partnersInfo.map((db): Partner => {
const adb2cUser = adb2cUsers.find(
(adb2c) => db.primaryAccountExternalId === adb2c.id,
);
if (!adb2cUser) {
throw new Error(
`adb2c user not found. externalId: ${db.primaryAccountExternalId}`,
);
}
const primaryAdmin = adb2cUser.displayName;
const mail = adb2cUser.identities?.find(
(identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
)?.issuerAssignedId;
if (!mail) {
throw new Error(
`adb2c user mail not found. externalId: ${db.primaryAccountExternalId}`,
);
}
return {
name: db.name,
tier: db.tier,
accountId: db.accountId,
country: db.country,
primaryAdmin: primaryAdmin,
email: mail,
dealerManagement: db.dealerManagement,
};
});
return {
total: partnersRecords.total,
partners: partners,
};
} 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.getPartners.name}`,
);
}
}
/**
* アカウント情報を設定する
* @param context
* @param externalId
* @param tier
* @param delegationPermission
* @param primaryAdminUserId
* @param parentAccountId
* @param secondryAdminUserId
* @returns UpdateAccountInfoResponse
*/
async updateAccountInfo(
context: Context,
externalId: string,
tier: number,
delegationPermission: boolean,
primaryAdminUserId: number,
parentAccountId?: number,
secondryAdminUserId?: number,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.updateAccountInfo.name
} | params: { ` +
`externalId: ${externalId}, ` +
`tier: ${tier}, ` +
`delegationPermission: ${delegationPermission}, ` +
`primaryAdminUserId: ${primaryAdminUserId}, ` +
`parentAccountId: ${parentAccountId}, ` +
`secondryAdminUserId: ${secondryAdminUserId}, };`,
);
try {
const { account_id: accountId } =
await this.usersRepository.findUserByExternalId(context, externalId);
await this.accountRepository.updateAccountInfo(
context,
accountId,
tier,
delegationPermission,
primaryAdminUserId,
parentAccountId,
secondryAdminUserId,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case DealerAccountNotFoundError:
case AdminUserNotFoundError:
throw new HttpException(
makeErrorResponse('E010502'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.updateAccountInfo.name}`,
);
}
}
/**
* アカウントと紐づくデータを削除する
* @param context
* @param externalId
* @param accountId // 削除対象のアカウントID
*/
async deleteAccountAndData(
context: Context,
externalId: string,
accountId: number,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.deleteAccountAndData.name
} | params: { ` +
`externalId: ${externalId}, ` +
`accountId: ${accountId}, };`,
);
let country: string;
let dbUsers: User[];
try {
// パラメータとトークンから取得したアカウントIDの突き合わせ
const { account_id: myAccountId } =
await this.usersRepository.findUserByExternalId(context, externalId);
if (myAccountId !== accountId) {
throw new HttpException(
makeErrorResponse('E000108'),
HttpStatus.UNAUTHORIZED,
);
}
// アカウント削除前に必要な情報を退避する
const targetAccount = await this.accountRepository.findAccountById(
context,
accountId,
);
// 削除対象アカウントを削除する
dbUsers = await this.accountRepository.deleteAccountAndInsertArchives(
context,
accountId,
);
this.logger.log(
`[${context.getTrackingId()}] delete account: ${accountId}`,
);
country = targetAccount.country;
} catch (e) {
// アカウントの削除に失敗した場合はエラーを返す
this.logger.log(`[${context.getTrackingId()}] ${e}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.deleteAccountAndData.name}`,
);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
try {
// 削除対象アカウント内のADB2Cユーザーをすべて削除する
await this.adB2cService.deleteUsers(
dbUsers.map((x) => x.external_id),
context,
);
this.logger.log(
`[${context.getTrackingId()}] delete ADB2C users: ${accountId}, users_id: ${dbUsers.map(
(x) => x.external_id,
)}`,
);
} catch (e) {
// ADB2Cユーザーの削除失敗時は、MANUAL_RECOVERY_REQUIREDを出して処理続行
this.logger.log(`[${context.getTrackingId()}] ${e}`);
this.logger.log(
`${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete ADB2C users: ${accountId}, users_id: ${dbUsers.map(
(x) => x.external_id,
)}`,
);
}
try {
// blobstorageコンテナを削除する
await this.deleteBlobContainer(accountId, country, context);
this.logger.log(
`[${context.getTrackingId()}] delete blob container: ${accountId}-${country}`,
);
} catch (e) {
// blobstorageコンテナを削除で失敗した場合は、MANUAL_RECOVERY_REQUIRED出して正常終了
this.logger.log(`[${context.getTrackingId()}] ${e}`);
this.logger.log(
`${MANUAL_RECOVERY_REQUIRED}[${context.getTrackingId()}] Failed to delete blob container: ${accountId}, country: ${country}`,
);
}
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.deleteAccountAndData.name}`,
);
}
/**
* IDトークンのsubからアカウントの階層情報を取得します
* @param context
* @param externalId
* @returns account info minimal access
*/
async getAccountInfoMinimalAccess(
context: Context,
externalId: string,
): Promise<number> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getAccountInfoMinimalAccess.name
} | params: { externalId: ${externalId} };`,
);
try {
const { account } = await this.usersRepository.findUserByExternalId(
context,
externalId,
);
if (!account) {
throw new AccountNotFoundError(
`Account not found. externalId: ${externalId}`,
);
}
return account.tier;
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case UserNotFoundError:
throw new HttpException(
makeErrorResponse('E010204'),
HttpStatus.BAD_REQUEST,
);
case AccountNotFoundError:
throw new HttpException(
makeErrorResponse('E010501'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${
this.getAccountInfoMinimalAccess.name
}`,
);
}
}
/**
* 自アカウントの会社名を取得する
* @param accountId
* @returns CompanyName
*/
async getCompanyName(
context: Context,
accountId: number,
): Promise<GetCompanyNameResponse> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getCompanyName.name
} | params: { accountId: ${accountId}, };`,
);
try {
const { company_name } = await this.accountRepository.findAccountById(
context,
accountId,
);
return { companyName: company_name };
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case AccountNotFoundError:
throw new HttpException(
makeErrorResponse('E010501'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getCompanyName.name}`,
);
}
}
}