Merged PR 324: API修正(ライセンス割り当てAPI)_履歴部分

## 概要
[Task2366: API修正(ライセンス割り当てAPI)_履歴部分](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2366)

- 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず)
- 何をどう変更したか、追加したライブラリなど
allocateLicenseの割り当て履歴テーブルのレコードを作成するロジックを追加しました。
createLicenseの引数を追加して、種別を指定して作成できるようにしました。
既存テストで項目の値などでべた書きしていた箇所を、定数を使うよう修正しました。
- このPull Requestでの対象/対象外
- 影響範囲(他の機能にも影響があるか)
createLicenseの引数を追加して、種別を指定して作成できるようにしました。
ユニットテストでcreateLicenseを使っている既存テストは再テスト済み

## レビューポイント
- 特にレビューしてほしい箇所
- 軽微なものや自明なものは記載不要
- 修正範囲が大きい場合などに記載
- 全体的にや仕様を満たしているか等は本当に必要な時のみ記載
lisence_allocation_historyテーブルのスキーマを変更しています。
プライマリとしてのidを追加(ユーザIDになっていた)、
allocate_typeをallocatedに変更して、boolの値で管理するように変更(割り当て解除or割り当てしかもたないので)。

## UIの変更
なし

## 動作確認状況
- ローカルで確認

## 補足
- 相談、参考資料などがあれば
This commit is contained in:
maruyama.t 2023-08-22 06:06:22 +00:00 committed by oura.a
parent f16a5414dc
commit bdd10aabf6
7 changed files with 414 additions and 25 deletions

View File

@ -1,8 +1,9 @@
-- +migrate Up
CREATE TABLE IF NOT EXISTS `lisence_allocation_history` (
`user_id` BIGINT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY COMMENT 'ユーザーID',
CREATE TABLE IF NOT EXISTS `license_allocation_history` (
`id` BIGINT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY COMMENT '割り当て履歴ID',
`user_id` BIGINT UNSIGNED NOT NULL COMMENT 'ユーザーID',
`license_id` BIGINT UNSIGNED NOT NULL COMMENT 'ライセンスID',
`allocate_type` VARCHAR(255) NOT NULL COMMENT '割り当て種別(割当解除/割当)',
`is_allocated` BOOLEAN NOT NULL DEFAULT 0 COMMENT '割り当て済みか',
`executed_at` TIMESTAMP NOT NULL COMMENT '実施日時',
`switch_from_type` VARCHAR(255) NOT NULL COMMENT '切り替え元種別(特になし/カード/トライアル)',
`deleted_at` TIMESTAMP COMMENT '削除時刻',
@ -13,4 +14,4 @@ CREATE TABLE IF NOT EXISTS `lisence_allocation_history` (
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci;
-- +migrate Down
DROP TABLE `lisence_allocation_history`;
DROP TABLE `license_allocation_history`;

View File

@ -119,6 +119,15 @@ export const LICENSE_ALLOCATED_STATUS = {
REUSABLE: 'Reusable',
DELETED: 'Deleted',
} as const;
/**
*
* @const {string[]}
*/
export const SWITCH_FROM_TYPE = {
NONE: 'NONE',
CARD: 'CARD',
TRIAL: 'TRIAL',
} as const;
/**
*

View File

@ -28,12 +28,16 @@ import {
createCardLicense,
createLicense,
createCardLicenseIssue,
createLicenseAllocationHistory,
selectCardLicensesCount,
selectCardLicense,
selectLicense,
selectLicenseAllocationHistory,
} from './test/utility';
import { UsersService } from '../users/users.service';
import { makeContext } from '../../common/log';
import { IsNotIn } from 'class-validator';
import { LICENSE_ALLOCATED_STATUS, LICENSE_TYPE } from '../../constants';
describe('LicensesService', () => {
it('ライセンス注文が完了する', async () => {
@ -327,18 +331,19 @@ describe('DBテスト', () => {
const cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
const defaultAccountId = 150;
const licenseId = 50;
const license_id = 50;
const issueId = 100;
await createLicense(
source,
licenseId,
license_id,
null,
defaultAccountId,
'Unallocated',
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
null,
);
await createCardLicense(source, licenseId, issueId, cardLicenseKey);
await createCardLicense(source, license_id, issueId, cardLicenseKey);
await createCardLicenseIssue(source, issueId);
const service = module.get<LicensesService>(LicensesService);
@ -348,7 +353,7 @@ describe('DBテスト', () => {
source,
cardLicenseKey,
);
const dbSelectResultFromLicense = await selectLicense(source, licenseId);
const dbSelectResultFromLicense = await selectLicense(source, license_id);
expect(
dbSelectResultFromCardLicense.cardLicense.activated_at,
).toBeDefined();
@ -379,19 +384,44 @@ describe('ライセンス割り当て', () => {
const { accountId } = await createAccount(source);
const { userId } = await createUser(source, accountId, 'userId', 'admin');
await createLicense(source, 1, null, accountId, 'Unallocated', null);
await createLicense(
source,
1,
null,
accountId,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
null,
);
await createLicenseAllocationHistory(source, 1, userId, 1, 'NONE');
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(
const resultLicense = await selectLicense(source, 1);
expect(resultLicense.license.allocated_user_id).toBe(userId);
expect(resultLicense.license.status).toBe(
LICENSE_ALLOCATED_STATUS.ALLOCATED,
);
expect(resultLicense.license.expiry_date.setMilliseconds(0)).toEqual(
expiry_date.setMilliseconds(0),
);
const licenseAllocationHistory = await selectLicenseAllocationHistory(
source,
userId,
1,
);
expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe(
userId,
);
expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe(
1,
);
expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe(
true,
);
});
it('再割り当て可能なライセンスに対して、ライセンス割り当てが完了する', async () => {
@ -401,15 +431,38 @@ describe('ライセンス割り当て', () => {
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);
await createLicense(
source,
1,
date,
accountId,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.REUSABLE,
null,
);
await createLicenseAllocationHistory(source, 1, userId, 1, 'NONE');
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.status).toBe(LICENSE_ALLOCATED_STATUS.ALLOCATED);
expect(result.license.expiry_date).toEqual(date);
const licenseAllocationHistory = await selectLicenseAllocationHistory(
source,
userId,
1,
);
expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe(
userId,
);
expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe(
1,
);
expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe(
true,
);
});
it('未割当のライセンスに対して、別のライセンスが割り当てられているユーザーの割り当てが完了する', async () => {
@ -419,8 +472,25 @@ describe('ライセンス割り当て', () => {
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);
await createLicense(
source,
1,
date,
accountId,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
userId,
);
await createLicense(
source,
2,
null,
accountId,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
null,
);
await createLicenseAllocationHistory(source, 1, userId, 1, 'NONE');
const service = module.get<UsersService>(UsersService);
@ -431,16 +501,164 @@ describe('ライセンス割り当て', () => {
// もともと割り当てられていたライセンスの状態確認
const result1 = await selectLicense(source, 1);
expect(result1.license.allocated_user_id).toBe(null);
expect(result1.license.status).toBe('Reusable');
expect(result1.license.status).toBe(LICENSE_ALLOCATED_STATUS.REUSABLE);
expect(result1.license.expiry_date).toEqual(date);
const licenseAllocationHistory = await selectLicenseAllocationHistory(
source,
userId,
1,
);
expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe(
userId,
);
expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe(
1,
);
expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe(
false,
);
// 新たに割り当てたライセンスの状態確認
const result2 = await selectLicense(source, 2);
expect(result2.license.allocated_user_id).toBe(userId);
expect(result2.license.status).toBe('Allocated');
expect(result2.license.status).toBe(LICENSE_ALLOCATED_STATUS.ALLOCATED);
expect(result2.license.expiry_date.setMilliseconds(0)).toEqual(
expiry_date.setMilliseconds(0),
);
const newlicenseAllocationHistory = await selectLicenseAllocationHistory(
source,
userId,
2,
);
expect(newlicenseAllocationHistory.licenseAllocationHistory.user_id).toBe(
userId,
);
expect(
newlicenseAllocationHistory.licenseAllocationHistory.license_id,
).toBe(2);
expect(
newlicenseAllocationHistory.licenseAllocationHistory.is_allocated,
).toBe(true);
});
it('割り当て時にライセンス履歴テーブルへの登録が完了する元がNORMALのとき', 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,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
userId,
);
await createLicense(
source,
2,
null,
accountId,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
null,
);
await createLicenseAllocationHistory(source, 1, userId, 1, 'NONE');
const service = module.get<UsersService>(UsersService);
await service.allocateLicense(makeContext('trackingId'), userId, 2);
const licenseAllocationHistory = await selectLicenseAllocationHistory(
source,
userId,
2,
);
expect(
licenseAllocationHistory.licenseAllocationHistory.switch_from_type,
).toBe('NONE');
});
it('割り当て時にライセンス履歴テーブルへの登録が完了する元がCARDのとき', 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,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
userId,
);
await createLicense(
source,
2,
null,
accountId,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
null,
);
await createLicenseAllocationHistory(source, 1, userId, 1, 'CARD');
const service = module.get<UsersService>(UsersService);
await service.allocateLicense(makeContext('trackingId'), userId, 2);
const licenseAllocationHistory = await selectLicenseAllocationHistory(
source,
userId,
2,
);
expect(
licenseAllocationHistory.licenseAllocationHistory.switch_from_type,
).toBe('CARD');
});
it('割り当て時にライセンス履歴テーブルへの登録が完了する元がTRIALのとき', 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,
LICENSE_TYPE.TRIAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
userId,
);
await createLicense(
source,
2,
null,
accountId,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
null,
);
await createLicenseAllocationHistory(source, 1, userId, 1, 'TRIAL');
const service = module.get<UsersService>(UsersService);
await service.allocateLicense(makeContext('trackingId'), userId, 2);
const licenseAllocationHistory = await selectLicenseAllocationHistory(
source,
userId,
2,
);
expect(
licenseAllocationHistory.licenseAllocationHistory.switch_from_type,
).toBe('TRIAL');
});
it('有効期限が切れているライセンスを割り当てようとした場合、エラーになる', async () => {
@ -450,7 +668,15 @@ describe('ライセンス割り当て', () => {
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);
await createLicense(
source,
1,
date,
accountId,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.REUSABLE,
null,
);
const service = module.get<UsersService>(UsersService);
@ -468,8 +694,24 @@ describe('ライセンス割り当て', () => {
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);
await createLicense(
source,
1,
null,
accountId,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
null,
);
await createLicense(
source,
2,
null,
accountId,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.DELETED,
null,
);
const service = module.get<UsersService>(UsersService);

View File

@ -5,6 +5,7 @@ import {
License,
CardLicense,
CardLicenseIssue,
LicenseAllocationHistory,
} from '../../../repositories/licenses/entity/license.entity';
export const createAccount = async (
@ -60,6 +61,7 @@ export const createLicense = async (
licenseId: number,
expiry_date: Date,
accountId: number,
type: string,
status: string,
allocated_user_id: number,
): Promise<void> => {
@ -67,7 +69,7 @@ export const createLicense = async (
id: licenseId,
expiry_date: expiry_date,
account_id: accountId,
type: 'card',
type: type,
status: status,
allocated_user_id: allocated_user_id,
order_id: null,
@ -117,6 +119,31 @@ export const createCardLicenseIssue = async (
identifiers.pop() as CardLicenseIssue;
};
export const createLicenseAllocationHistory = async (
datasource: DataSource,
historyId: number,
userId: number,
licenseId: number,
type: string,
): Promise<void> => {
const { identifiers } = await datasource
.getRepository(LicenseAllocationHistory)
.insert({
id: historyId,
user_id: userId,
license_id: licenseId,
is_allocated: true,
executed_at: new Date(),
switch_from_type: type,
deleted_at: null,
created_by: null,
created_at: new Date(),
updated_by: null,
updated_at: new Date(),
});
identifiers.pop() as LicenseAllocationHistory;
};
export const selectCardLicensesCount = async (
datasource: DataSource,
): Promise<{ count: number }> => {
@ -147,3 +174,22 @@ export const selectLicense = async (
});
return { license };
};
export const selectLicenseAllocationHistory = async (
datasource: DataSource,
userId: number,
licence_id: number,
): Promise<{ licenseAllocationHistory: LicenseAllocationHistory }> => {
const licenseAllocationHistory = await datasource
.getRepository(LicenseAllocationHistory)
.findOne({
where: {
user_id: userId,
license_id: licence_id,
},
order: {
executed_at: 'DESC',
},
});
return { licenseAllocationHistory };
};

View File

@ -6,6 +6,7 @@ import {
UpdateDateColumn,
OneToOne,
JoinColumn,
ManyToOne,
} from 'typeorm';
import { User } from '../../users/entity/user.entity';
@ -164,3 +165,43 @@ export class CardLicense {
@UpdateDateColumn({})
updated_at: Date;
}
@Entity({ name: 'license_allocation_history' })
export class LicenseAllocationHistory {
@PrimaryGeneratedColumn()
id: number;
@Column()
user_id: number;
@Column()
license_id: number;
@Column()
is_allocated: boolean;
@Column()
executed_at: Date;
@Column()
switch_from_type: string;
@Column({ nullable: true })
deleted_at: Date;
@Column({ nullable: true })
created_by: string;
@CreateDateColumn()
created_at: Date;
@Column({ nullable: true })
updated_by: string;
@UpdateDateColumn()
updated_at: Date;
@ManyToOne(() => License, (licenses) => licenses.id)
@JoinColumn({ name: 'license_id' })
license?: License;
}

View File

@ -5,6 +5,7 @@ import {
CardLicenseIssue,
License,
LicenseOrder,
LicenseAllocationHistory,
} from './entity/license.entity';
import { LicensesRepositoryService } from './licenses.repository.service';
@ -15,6 +16,7 @@ import { LicensesRepositoryService } from './licenses.repository.service';
License,
CardLicense,
CardLicenseIssue,
LicenseAllocationHistory,
]),
],
providers: [LicensesRepositoryService],

View File

@ -5,6 +5,7 @@ import {
License,
CardLicenseIssue,
CardLicense,
LicenseAllocationHistory,
} from './entity/license.entity';
import {
CARD_LICENSE_LENGTH,
@ -12,6 +13,7 @@ import {
LICENSE_STATUS_ISSUE_REQUESTING,
LICENSE_STATUS_ISSUED,
LICENSE_TYPE,
SWITCH_FROM_TYPE,
TIERS,
} from '../../constants';
import {
@ -400,8 +402,11 @@ export class LicensesRepositoryService {
*/
async allocateLicense(userId: number, newLicenseId: number): Promise<void> {
await this.dataSource.transaction(async (entityManager) => {
// 割り当て対象のライセンス情報を取得
const licenseRepo = entityManager.getRepository(License);
const licenseAllocationHistoryRepo = entityManager.getRepository(
LicenseAllocationHistory,
);
// 割り当て対象のライセンス情報を取得
const targetLicense = await licenseRepo.findOne({
where: {
id: newLicenseId,
@ -439,7 +444,17 @@ export class LicensesRepositoryService {
if (allocatedLicense) {
allocatedLicense.status = LICENSE_ALLOCATED_STATUS.REUSABLE;
allocatedLicense.allocated_user_id = null;
await licenseRepo.save(allocatedLicense);
// ライセンス割り当て履歴テーブルへ登録
const deallocationHistory = new LicenseAllocationHistory();
deallocationHistory.user_id = userId;
deallocationHistory.license_id = allocatedLicense.id;
deallocationHistory.is_allocated = false;
deallocationHistory.executed_at = new Date();
deallocationHistory.switch_from_type = SWITCH_FROM_TYPE.NONE;
await licenseAllocationHistoryRepo.save(deallocationHistory);
}
// ライセンス割り当てを実施
@ -450,6 +465,39 @@ export class LicensesRepositoryService {
targetLicense.expiry_date = new NewAllocatedLicenseExpirationDate();
}
await licenseRepo.save(targetLicense);
// 直近割り当てたライセンス種別を取得
const oldLicenseType = await licenseAllocationHistoryRepo.findOne({
relations: {
license: true,
},
where: { user_id: userId, is_allocated: true },
order: { executed_at: 'DESC' },
});
let switchFromType = '';
switch (oldLicenseType.license.type) {
case LICENSE_TYPE.CARD:
switchFromType = SWITCH_FROM_TYPE.CARD;
break;
case LICENSE_TYPE.TRIAL:
switchFromType = SWITCH_FROM_TYPE.TRIAL;
break;
default:
switchFromType = SWITCH_FROM_TYPE.NONE;
break;
}
// ライセンス割り当て履歴テーブルへ登録
const allocationHistory = new LicenseAllocationHistory();
allocationHistory.user_id = userId;
allocationHistory.license_id = targetLicense.id;
allocationHistory.is_allocated = true;
allocationHistory.executed_at = new Date();
// TODO switchFromTypeの値については「PBI1234: 第一階層として、ライセンス数推移情報をCSV出力したい」で正式対応
allocationHistory.switch_from_type = switchFromType;
await licenseAllocationHistoryRepo.save(allocationHistory);
});
}
}