Merged PR 320: API実装(ライセンス割り当てAPI)_履歴以外

## 概要
[Task2362: API実装(ライセンス割り当てAPI)_履歴以外](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2362)

ライセンス割り当てのAPIを作成しました。
※ライセンス割り当て履歴テーブルが絡む処理は別タスクでの対応となるので、ここでは未実装です。

## レビューポイント
なし

## UIの変更
なし

## 動作確認状況
ローカルでUT、動作確認実施済み

## 補足
なし
This commit is contained in:
oura.a 2023-08-10 08:26:44 +00:00
parent 3b785e97aa
commit 356f5fe346
19 changed files with 383 additions and 24 deletions

View File

@ -44,4 +44,6 @@ export const ErrorCodes = [
'E010802', // ライセンス取り込み済みエラー
'E010803', // ライセンス発行済みエラー
'E010804', // ライセンス不足エラー
'E010805', // ライセンス有効期限切れエラー
'E010806', // ライセンス割り当て不可エラー
] as const;

View File

@ -33,4 +33,6 @@ export const errors: Errors = {
E010802: 'License already activated Error',
E010803: 'License already issued Error',
E010804: 'License shortage Error',
E010805: 'License is expired Error',
E010806: 'License is unavailable Error',
};

View File

@ -126,6 +126,12 @@ export const LICENSE_ALLOCATED_STATUS = {
*/
export const LICENSE_EXPIRATION_THRESHOLD_DAYS = 14;
/**
*
* @const {number}
*/
export const LICENSE_EXPIRATION_DAYS = 365;
/**
*
* @const {number}

View File

@ -580,7 +580,6 @@ describe('getPartnerAccount', () => {
});
});
describe('getOrderHistories', () => {
let source: DataSource = null;
beforeEach(async () => {

View File

@ -126,4 +126,4 @@ export const createUser = async (
});
const user = identifiers.pop() as User;
return { userId: user.id, externalId: external_id, authorId: author_id };
};
};

View File

@ -191,15 +191,15 @@ export class GetPartnerLicensesResponse {
// 第五階層のshortage算出用
export class PartnerLicenseInfoForShortage {
expiringSoonLicense?:number;
allocatableLicenseWithMargin?:number;
expiringSoonLicense?: number;
allocatableLicenseWithMargin?: number;
}
// RepositoryからPartnerLicenseInfoに関する情報を取得する際の型
export type PartnerLicenseInfoForRepository = Omit<
PartnerLicenseInfo & PartnerLicenseInfoForShortage,
'shortage'
>;
>;
export class GetOrderHistoriesRequest {
@ApiProperty({ description: '取得件数' })

View File

@ -202,7 +202,6 @@ export class LicensesController {
@Req() req: Request,
@Body() body: GetAllocatableLicensesRequest,
): Promise<GetAllocatableLicensesResponse> {
// TODO 仮の戻り値
return {
allocatableLicenses: [

View File

@ -4,6 +4,7 @@ import {
IssueCardLicensesRequest,
IssueCardLicensesResponse,
ActivateCardLicensesRequest,
NewAllocatedLicenseExpirationDate,
} from './types/types';
import {
makeDefaultAccountsRepositoryMockValue,
@ -31,6 +32,8 @@ import {
selectCardLicense,
selectLicense,
} from './test/utility';
import { UsersService } from '../users/users.service';
import { makeContext } from '../../common/log';
describe('LicensesService', () => {
it('ライセンス注文が完了する', async () => {
@ -327,7 +330,14 @@ describe('DBテスト', () => {
const licenseId = 50;
const issueId = 100;
await createLicense(source, licenseId, defaultAccountId);
await createLicense(
source,
licenseId,
null,
defaultAccountId,
'Unallocated',
null,
);
await createCardLicense(source, licenseId, issueId, cardLicenseKey);
await createCardLicenseIssue(source, issueId);
@ -345,3 +355,133 @@ describe('DBテスト', () => {
expect(dbSelectResultFromLicense.license.account_id).toEqual(accountId);
});
});
describe('ライセンス割り当て', () => {
let source: DataSource = null;
beforeEach(async () => {
source = new DataSource({
type: 'sqlite',
database: ':memory:',
logging: false,
entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
synchronize: true, // trueにすると自動的にmigrationが行われるため注意
});
return source.initialize();
});
afterEach(async () => {
await source.destroy();
source = null;
});
it('未割当のライセンスに対して、ライセンス割り当てが完了する', async () => {
const module = await makeTestingModule(source);
const { accountId } = await createAccount(source);
const { userId } = await createUser(source, accountId, 'userId', 'admin');
await createLicense(source, 1, null, accountId, 'Unallocated', null);
const service = module.get<UsersService>(UsersService);
const expiry_date = new NewAllocatedLicenseExpirationDate();
await service.allocateLicense(makeContext('trackingId'), userId, 1);
const result = await selectLicense(source, 1);
expect(result.license.allocated_user_id).toBe(userId);
expect(result.license.status).toBe('Allocated');
expect(result.license.expiry_date.setMilliseconds(0)).toEqual(
expiry_date.setMilliseconds(0),
);
});
it('再割り当て可能なライセンスに対して、ライセンス割り当てが完了する', async () => {
const module = await makeTestingModule(source);
const { accountId } = await createAccount(source);
const { userId } = await createUser(source, accountId, 'userId', 'admin');
const date = new Date();
date.setDate(date.getDate() + 30);
await createLicense(source, 1, date, accountId, 'Reusable', null);
const service = module.get<UsersService>(UsersService);
await service.allocateLicense(makeContext('trackingId'), userId, 1);
const result = await selectLicense(source, 1);
expect(result.license.allocated_user_id).toBe(userId);
expect(result.license.status).toBe('Allocated');
expect(result.license.expiry_date).toEqual(date);
});
it('未割当のライセンスに対して、別のライセンスが割り当てられているユーザーの割り当てが完了する', async () => {
const module = await makeTestingModule(source);
const { accountId } = await createAccount(source);
const { userId } = await createUser(source, accountId, 'userId', 'admin');
const date = new Date();
date.setDate(date.getDate() + 30);
await createLicense(source, 1, date, accountId, 'Allocated', userId);
await createLicense(source, 2, null, accountId, 'Unallocated', null);
const service = module.get<UsersService>(UsersService);
const expiry_date = new NewAllocatedLicenseExpirationDate();
await service.allocateLicense(makeContext('trackingId'), userId, 2);
// もともと割り当てられていたライセンスの状態確認
const result1 = await selectLicense(source, 1);
expect(result1.license.allocated_user_id).toBe(null);
expect(result1.license.status).toBe('Reusable');
expect(result1.license.expiry_date).toEqual(date);
// 新たに割り当てたライセンスの状態確認
const result2 = await selectLicense(source, 2);
expect(result2.license.allocated_user_id).toBe(userId);
expect(result2.license.status).toBe('Allocated');
expect(result2.license.expiry_date.setMilliseconds(0)).toEqual(
expiry_date.setMilliseconds(0),
);
});
it('有効期限が切れているライセンスを割り当てようとした場合、エラーになる', async () => {
const module = await makeTestingModule(source);
const { accountId } = await createAccount(source);
const { userId } = await createUser(source, accountId, 'userId', 'admin');
const date = new Date();
date.setDate(date.getDate() - 30);
await createLicense(source, 1, date, accountId, 'Reusable', null);
const service = module.get<UsersService>(UsersService);
await expect(
service.allocateLicense(makeContext('trackingId'), userId, 1),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010805'), HttpStatus.BAD_REQUEST),
);
});
it('割り当て不可なライセンスを割り当てようとした場合、エラーになる', async () => {
const module = await makeTestingModule(source);
const { accountId } = await createAccount(source);
const { userId } = await createUser(source, accountId, 'userId', 'admin');
const date = new Date();
date.setDate(date.getDate() + 30);
await createLicense(source, 1, null, accountId, 'Allocated', null);
await createLicense(source, 2, null, accountId, 'Deleted', null);
const service = module.get<UsersService>(UsersService);
await expect(
service.allocateLicense(makeContext('trackingId'), userId, 1),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010806'), HttpStatus.BAD_REQUEST),
);
await expect(
service.allocateLicense(makeContext('trackingId'), userId, 2),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010806'), HttpStatus.BAD_REQUEST),
);
});
});

View File

@ -58,15 +58,18 @@ export const createUser = async (
export const createLicense = async (
datasource: DataSource,
licenseId: number,
expiry_date: Date,
accountId: number,
status: string,
allocated_user_id: number,
): Promise<void> => {
const { identifiers } = await datasource.getRepository(License).insert({
id: licenseId,
expiry_date: null,
expiry_date: expiry_date,
account_id: accountId,
type: 'card',
status: 'Unallocated',
allocated_user_id: null,
status: status,
allocated_user_id: allocated_user_id,
order_id: null,
deleted_at: null,
delete_order_id: null,

View File

@ -1,6 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsInt, Matches, Max, Min, Length } from 'class-validator';
import { LICENSE_EXPIRATION_THRESHOLD_DAYS } from '../../../constants';
import {
LICENSE_EXPIRATION_DAYS,
LICENSE_EXPIRATION_THRESHOLD_DAYS,
} from '../../../constants';
export class CreateOrdersRequest {
@ApiProperty()
@ -74,4 +77,18 @@ export class ExpirationThresholdDate extends Date {
this.setDate(this.getDate() + LICENSE_EXPIRATION_THRESHOLD_DAYS);
this.setHours(23, 59, 59, 999); // 時分秒を"23:59:59.999"に固定
}
}
}
// 新規ライセンス割り当て時の有効期限算出用に、365日後の日付を取得する
export class NewAllocatedLicenseExpirationDate extends Date {
constructor(...args: any[]) {
if (args.length === 0) {
super(); // 引数がない場合、現在の日付で初期化
} else {
super(...(args as [string])); // 引数がある場合、引数をそのままDateクラスのコンストラクタに渡す
}
this.setDate(this.getDate() + LICENSE_EXPIRATION_DAYS);
this.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
this.setMilliseconds(0);
}
}

View File

@ -8,6 +8,7 @@ import {
import { SendGridService } from '../../../gateways/sendgrid/sendgrid.service';
import { User } from '../../../repositories/users/entity/user.entity';
import { UsersRepositoryService } from '../../../repositories/users/users.repository.service';
import { LicensesRepositoryService } from '../../../repositories/licenses/licenses.repository.service';
import { UsersService } from '../users.service';
import { SortCriteria } from '../../../repositories/sort_criteria/entity/sort_criteria.entity';
import { SortCriteriaRepositoryService } from '../../../repositories/sort_criteria/sort_criteria.repository.service';
@ -31,6 +32,10 @@ export type UsersRepositoryMockValue = {
existsAuthorId: boolean | Error;
};
export type LicensesRepositoryMockValue = {
// empty
};
export type AdB2cMockValue = {
getMetaData: B2cMetadata | Error;
getSignKeySets: JwkSignKey[] | Error;
@ -58,6 +63,7 @@ export type ConfigMockValue = {
export const makeUsersServiceMock = async (
usersRepositoryMockValue: UsersRepositoryMockValue,
licensesRepositoryMockValue: LicensesRepositoryMockValue,
adB2cMockValue: AdB2cMockValue,
sendGridMockValue: SendGridMockValue,
configMockValue: ConfigMockValue,
@ -75,6 +81,8 @@ export const makeUsersServiceMock = async (
switch (token) {
case UsersRepositoryService:
return makeUsersRepositoryMock(usersRepositoryMockValue);
case LicensesRepositoryService:
return makeLicensesRepositoryMock();
case AdB2cService:
return makeAdB2cServiceMock(adB2cMockValue);
case SendGridService:
@ -239,6 +247,12 @@ export const makeUsersRepositoryMock = (value: UsersRepositoryMockValue) => {
};
};
export const makeLicensesRepositoryMock = (): LicensesRepositoryMockValue => {
return {
// empty
};
};
export const makeSendGridMock = (value: SendGridMockValue) => {
const { sendMail, createMailContentFromEmailConfirmForNormalUser } = value;

View File

@ -247,4 +247,4 @@ export class AllocateLicenseRequest {
newLicenseId: number;
}
export class AllocateLicenseResponse{}
export class AllocateLicenseResponse {}

View File

@ -409,7 +409,15 @@ export class UsersController {
@Body() body: AllocateLicenseRequest,
@Req() req: Request,
): Promise<AllocateLicenseResponse> {
const accessToken = retrieveAuthorizationToken(req);
const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken;
const context = makeContext(userId);
await this.usersService.allocateLicense(
context,
body.userId,
body.newLicenseId,
);
return {};
}
}

View File

@ -4,12 +4,14 @@ import { AdB2cModule } from '../../gateways/adb2c/adb2c.module';
import { SendGridModule } from '../../gateways/sendgrid/sendgrid.module';
import { SortCriteriaRepositoryModule } from '../../repositories/sort_criteria/sort_criteria.repository.module';
import { UsersRepositoryModule } from '../../repositories/users/users.repository.module';
import { LicensesRepositoryModule } from '../../repositories/licenses/licenses.repository.module';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
imports: [
UsersRepositoryModule,
LicensesRepositoryModule,
SortCriteriaRepositoryModule,
AdB2cModule,
SendGridModule,

View File

@ -30,6 +30,7 @@ import { makeTestingModule } from '../../common/test/modules';
describe('UsersService.confirmUser', () => {
it('ユーザの仮登録時に払い出されるトークンにより、未認証のユーザが認証済みになる', async () => {
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
const sendGridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
@ -37,6 +38,7 @@ describe('UsersService.confirmUser', () => {
makeDefaultSortCriteriaRepositoryMockValue();
const service = await makeUsersServiceMock(
usersRepositoryMockValue,
licensesRepositoryMockValue,
adb2cParam,
sendGridMockValue,
configMockValue,
@ -66,6 +68,7 @@ describe('UsersService.confirmUser', () => {
encryption: false,
prompt: false,
};
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
const configMockValue = makeDefaultConfigValue();
const sortCriteriaRepositoryMockValue =
@ -73,6 +76,7 @@ describe('UsersService.confirmUser', () => {
const sendGridMockValue = makeDefaultSendGridlValue();
const service = await makeUsersServiceMock(
usersRepositoryMockValue,
licensesRepositoryMockValue,
adb2cParam,
sendGridMockValue,
configMockValue,
@ -86,6 +90,7 @@ describe('UsersService.confirmUser', () => {
it('トークンの形式が不正な場合、形式不正エラーとなる。', async () => {
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
@ -93,6 +98,7 @@ describe('UsersService.confirmUser', () => {
makeDefaultSortCriteriaRepositoryMockValue();
const service = await makeUsersServiceMock(
usersRepositoryMockValue,
licensesRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
@ -123,6 +129,7 @@ describe('UsersService.confirmUser', () => {
encryption: false,
prompt: false,
};
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
const sendGridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
@ -130,6 +137,7 @@ describe('UsersService.confirmUser', () => {
makeDefaultSortCriteriaRepositoryMockValue();
const service = await makeUsersServiceMock(
usersRepositoryMockValue,
licensesRepositoryMockValue,
adb2cParam,
sendGridMockValue,
configMockValue,
@ -142,6 +150,7 @@ describe('UsersService.confirmUser', () => {
});
it('ユーザが既に認証済みだった場合、認証済みユーザエラーとなる。', async () => {
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
@ -153,6 +162,7 @@ describe('UsersService.confirmUser', () => {
const service = await makeUsersServiceMock(
usersRepositoryMockValue,
licensesRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
@ -183,6 +193,7 @@ describe('UsersService.confirmUser', () => {
encryption: false,
prompt: false,
};
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
const sendGridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
@ -193,6 +204,7 @@ describe('UsersService.confirmUser', () => {
const service = await makeUsersServiceMock(
usersRepositoryMockValue,
licensesRepositoryMockValue,
adb2cParam,
sendGridMockValue,
configMockValue,
@ -206,6 +218,7 @@ describe('UsersService.confirmUser', () => {
});
it('DBネットワークエラーとなる場合、エラーとなる。', async () => {
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
@ -215,6 +228,7 @@ describe('UsersService.confirmUser', () => {
const service = await makeUsersServiceMock(
usersRepositoryMockValue,
licensesRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
@ -248,6 +262,7 @@ describe('UsersService.confirmUser', () => {
encryption: false,
prompt: false,
};
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
const sendGridMockValue = makeDefaultSendGridlValue();
usersRepositoryMockValue.updateUserVerified = new Error('DB error');
@ -256,6 +271,7 @@ describe('UsersService.confirmUser', () => {
makeDefaultSortCriteriaRepositoryMockValue();
const service = await makeUsersServiceMock(
usersRepositoryMockValue,
licensesRepositoryMockValue,
adb2cParam,
sendGridMockValue,
configMockValue,
@ -272,6 +288,7 @@ describe('UsersService.confirmUser', () => {
});
it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:None)', async () => {
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
@ -279,6 +296,7 @@ describe('UsersService.confirmUser', () => {
makeDefaultSortCriteriaRepositoryMockValue();
const service = await makeUsersServiceMock(
usersRepositoryMockValue,
licensesRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
@ -308,6 +326,7 @@ describe('UsersService.confirmUser', () => {
describe('UsersService.createUser', () => {
it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:Author; 暗号化あり)', async () => {
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
@ -315,6 +334,7 @@ describe('UsersService.createUser', () => {
makeDefaultSortCriteriaRepositoryMockValue();
const service = await makeUsersServiceMock(
usersRepositoryMockValue,
licensesRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
@ -350,6 +370,7 @@ describe('UsersService.createUser', () => {
it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:Author; 暗号化無し)', async () => {
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
@ -357,6 +378,7 @@ describe('UsersService.createUser', () => {
makeDefaultSortCriteriaRepositoryMockValue();
const service = await makeUsersServiceMock(
usersRepositoryMockValue,
licensesRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
@ -392,6 +414,7 @@ describe('UsersService.createUser', () => {
it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:Transcriptioninst)', async () => {
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
@ -399,6 +422,7 @@ describe('UsersService.createUser', () => {
makeDefaultSortCriteriaRepositoryMockValue();
const service = await makeUsersServiceMock(
usersRepositoryMockValue,
licensesRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
@ -426,6 +450,7 @@ describe('UsersService.createUser', () => {
});
it('DBネットワークエラーとなる場合、エラーとなる。', async () => {
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
@ -434,6 +459,7 @@ describe('UsersService.createUser', () => {
usersRepositoryMockValue.createNormalUser = new Error('DB error');
const service = await makeUsersServiceMock(
usersRepositoryMockValue,
licensesRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
@ -465,6 +491,7 @@ describe('UsersService.createUser', () => {
});
it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。', async () => {
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
adb2cParam.createUser = new Error();
const sendgridMockValue = makeDefaultSendGridlValue();
@ -473,6 +500,7 @@ describe('UsersService.createUser', () => {
makeDefaultSortCriteriaRepositoryMockValue();
const service = await makeUsersServiceMock(
usersRepositoryMockValue,
licensesRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
@ -504,6 +532,7 @@ describe('UsersService.createUser', () => {
});
it('メールアドレスが重複している場合、エラーとなる。', async () => {
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
adb2cParam.createUser = { reason: 'email', message: 'ObjectConflict' };
const sendgridMockValue = makeDefaultSendGridlValue();
@ -512,6 +541,7 @@ describe('UsersService.createUser', () => {
makeDefaultSortCriteriaRepositoryMockValue();
const service = await makeUsersServiceMock(
usersRepositoryMockValue,
licensesRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
@ -540,6 +570,7 @@ describe('UsersService.createUser', () => {
});
it('AuthorIDが重複している場合、エラーとなる。(AuthorID重複チェックでエラー)', async () => {
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
@ -550,6 +581,7 @@ describe('UsersService.createUser', () => {
const service = await makeUsersServiceMock(
usersRepositoryMockValue,
licensesRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
@ -580,6 +612,7 @@ describe('UsersService.createUser', () => {
});
it('AuthorIDが重複している場合、エラーとなる。(insert失敗)', async () => {
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
@ -590,6 +623,7 @@ describe('UsersService.createUser', () => {
const service = await makeUsersServiceMock(
usersRepositoryMockValue,
licensesRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
@ -882,6 +916,7 @@ describe('UsersService.getUsers', () => {
describe('UsersService.updateSortCriteria', () => {
it('ソート条件を変更できる', async () => {
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
@ -889,6 +924,7 @@ describe('UsersService.updateSortCriteria', () => {
makeDefaultSortCriteriaRepositoryMockValue();
const service = await makeUsersServiceMock(
usersRepositoryMockValue,
licensesRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
@ -906,6 +942,7 @@ describe('UsersService.updateSortCriteria', () => {
it('ユーザー情報が存在せず、ソート条件を変更できない', async () => {
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
@ -916,6 +953,7 @@ describe('UsersService.updateSortCriteria', () => {
const service = await makeUsersServiceMock(
usersRepositoryMockValue,
licensesRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
@ -938,6 +976,7 @@ describe('UsersService.updateSortCriteria', () => {
it('ソート条件が存在せず、ソート条件を変更できない', async () => {
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
@ -949,6 +988,7 @@ describe('UsersService.updateSortCriteria', () => {
const service = await makeUsersServiceMock(
usersRepositoryMockValue,
licensesRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
@ -973,6 +1013,7 @@ describe('UsersService.updateSortCriteria', () => {
describe('UsersService.getSortCriteria', () => {
it('ソート条件を取得できる', async () => {
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
@ -980,6 +1021,7 @@ describe('UsersService.getSortCriteria', () => {
makeDefaultSortCriteriaRepositoryMockValue();
const service = await makeUsersServiceMock(
usersRepositoryMockValue,
licensesRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
@ -997,6 +1039,7 @@ describe('UsersService.getSortCriteria', () => {
it('ソート条件が存在せず、ソート条件を取得できない', async () => {
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
@ -1009,6 +1052,7 @@ describe('UsersService.getSortCriteria', () => {
const service = await makeUsersServiceMock(
usersRepositoryMockValue,
licensesRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,
@ -1031,6 +1075,7 @@ describe('UsersService.getSortCriteria', () => {
it('DBから取得した値が不正だった場合、エラーとなる', async () => {
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue();
const sendgridMockValue = makeDefaultSendGridlValue();
const configMockValue = makeDefaultConfigValue();
@ -1045,6 +1090,7 @@ describe('UsersService.getSortCriteria', () => {
const service = await makeUsersServiceMock(
usersRepositoryMockValue,
licensesRepositoryMockValue,
adb2cParam,
sendgridMockValue,
configMockValue,

View File

@ -23,6 +23,7 @@ import {
newUser,
} from '../../repositories/users/entity/user.entity';
import { UsersRepositoryService } from '../../repositories/users/users.repository.service';
import { LicensesRepositoryService } from '../../repositories/licenses/licenses.repository.service';
import { GetRelationsResponse, User } from './types/types';
import {
AuthorIdAlreadyExistsError,
@ -39,11 +40,16 @@ import {
import { DateWithZeroTime } from '../licenses/types/types';
import { Context } from '../../common/log';
import { UserRoles } from '../../common/types/role';
import {
LicenseExpiredError,
LicenseUnavailableError,
} from '../../repositories/licenses/errors/types';
@Injectable()
export class UsersService {
constructor(
private readonly usersRepository: UsersRepositoryService,
private readonly licensesRepository: LicensesRepositoryService,
private readonly sortCriteriaRepository: SortCriteriaRepositoryService,
private readonly adB2cService: AdB2cService,
private readonly configService: ConfigService,
@ -805,4 +811,51 @@ export class UsersService {
this.logger.log(`[OUT] [${context.trackingId}] ${this.updateUser.name}`);
}
}
/**
*
* @param context
* @param userId
* @param newLicenseId
*/
async allocateLicense(
context: Context,
userId: number,
newLicenseId: number,
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.allocateLicense.name} | params: { ` +
`userId: ${userId}, ` +
`newLicenseId: ${newLicenseId}, `,
);
try {
await this.licensesRepository.allocateLicense(userId, newLicenseId);
} catch (e) {
this.logger.error(`error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case LicenseExpiredError:
throw new HttpException(
makeErrorResponse('E010805'),
HttpStatus.BAD_REQUEST,
);
case LicenseUnavailableError:
throw new HttpException(
makeErrorResponse('E010806'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.allocateLicense.name}`,
);
}
}
}

View File

@ -507,16 +507,16 @@ export class AccountsRepositoryService {
// 戻り値用の値を設定
const childPartnerLicenseFromRepository: PartnerLicenseInfoForRepository =
{
accountId: childAccount.id,
tier: childAccount.tier,
companyName: childAccount.company_name,
stockLicense: childLicenseOrderStatus.stockLicense,
issuedRequested: childLicenseOrderStatus.issuedRequested,
issueRequesting: childLicenseOrderStatus.issueRequesting,
expiringSoonLicense: expiringSoonLicense,
allocatableLicenseWithMargin: allocatableLicenseWithMargin,
};
{
accountId: childAccount.id,
tier: childAccount.tier,
companyName: childAccount.company_name,
stockLicense: childLicenseOrderStatus.stockLicense,
issuedRequested: childLicenseOrderStatus.issuedRequested,
issueRequesting: childLicenseOrderStatus.issueRequesting,
expiringSoonLicense: expiringSoonLicense,
allocatableLicenseWithMargin: allocatableLicenseWithMargin,
};
childPartnerLicensesFromRepository.push(
childPartnerLicenseFromRepository,

View File

@ -13,3 +13,8 @@ export class OrderNotFoundError extends Error {}
export class AlreadyIssuedError extends Error {}
// ライセンス不足エラー
export class LicensesShortageError extends Error {}
// ライセンス有効期限切れエラー
export class LicenseExpiredError extends Error {}
// ライセンス割り当て不可エラー
export class LicenseUnavailableError extends Error {}

View File

@ -21,7 +21,10 @@ import {
LicensesShortageError,
AlreadyIssuedError,
OrderNotFoundError,
LicenseExpiredError,
LicenseUnavailableError,
} from './errors/types';
import { NewAllocatedLicenseExpirationDate } from '../../features/licenses/types/types';
@Injectable()
export class LicensesRepositoryService {
@ -387,4 +390,64 @@ export class LicensesRepositoryService {
}
});
}
/**
*
* @param userId
* @param newLicenseId
*/
async allocateLicense(userId: number, newLicenseId: number): Promise<void> {
await this.dataSource.transaction(async (entityManager) => {
// 割り当て対象のライセンス情報を取得
const licenseRepo = entityManager.getRepository(License);
const targetLicense = await licenseRepo.findOne({
where: {
id: newLicenseId,
},
});
// 期限切れの場合はエラー
if (targetLicense.expiry_date) {
const currentDay = new Date();
currentDay.setHours(23, 59, 59, 999);
if (targetLicense.expiry_date < currentDay) {
throw new LicenseExpiredError(
`License is expired. expiration date: ${targetLicense.expiry_date} current Date: ${currentDay}`,
);
}
}
// ライセンス状態が「未割当」「再利用可能」以外の場合はエラー
if (
targetLicense.status === LICENSE_ALLOCATED_STATUS.ALLOCATED ||
targetLicense.status === LICENSE_ALLOCATED_STATUS.DELETED
) {
throw new LicenseUnavailableError(
`License is unavailable. License status: ${targetLicense.status}`,
);
}
// 対象ユーザーのライセンス割り当て状態を取得
const allocatedLicense = await licenseRepo.findOne({
where: {
allocated_user_id: userId,
},
});
// 既にライセンスが割り当てられているなら、割り当てを解除
if (allocatedLicense) {
allocatedLicense.status = LICENSE_ALLOCATED_STATUS.REUSABLE;
allocatedLicense.allocated_user_id = null;
await licenseRepo.save(allocatedLicense);
}
// ライセンス割り当てを実施
targetLicense.status = LICENSE_ALLOCATED_STATUS.ALLOCATED;
targetLicense.allocated_user_id = userId;
// 有効期限が未設定なら365日後に設定
if (!targetLicense.expiry_date) {
targetLicense.expiry_date = new NewAllocatedLicenseExpirationDate();
}
await licenseRepo.save(targetLicense);
});
}
}