From fd69541e1a12ab69e91796df37cc464af2e1a847 Mon Sep 17 00:00:00 2001 From: "maruyama.t" Date: Thu, 15 Jun 2023 08:56:03 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20142:=20API=E5=AE=9F=E8=A3=85?= =?UTF-8?q?=EF=BC=88=E7=AC=AC=E4=BA=94=E9=9A=8E=E5=B1=A4=E7=94=A8=E3=83=A9?= =?UTF-8?q?=E3=82=A4=E3=82=BB=E3=83=B3=E3=82=B9=E6=83=85=E5=A0=B1=E5=8F=96?= =?UTF-8?q?=E5=BE=97API=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task1846: API実装(第五階層用ライセンス情報取得API)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/1846) - 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず) - 何をどう変更したか、追加したライブラリなど accounts.controller.tsからaccountService.getLicenseSummaryを呼び出す。 一度のトランザクションで処理を行うよう、serviceとrepositoryをリファクタリング。 license.entity.tsにはライセンス系のテーブルで不足していたエンティティを追加。 - このPull Requestでの対象/対象外 Storage Sizeの値はPBI1203では対象外のため0固定 Used Sizeの値はPBI1203では対象外のため0固定 LicenseSummaryInfo2と定義している個所は、別タスクで修正します。 [タスク 1961: API IF修正(LicenseSummaryInfo)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/OMDSDictation/_sprints/taskboard/OMDSDictation%20%E3%83%81%E3%83%BC%E3%83%A0/OMDSDictation/%E3%82%B9%E3%83%97%E3%83%AA%E3%83%B3%E3%83%88%2011-1?workitem=1961) - 影響範囲(他の機能にも影響があるか) 新規追加のため、なし ## レビューポイント - 特にレビューしてほしい箇所 ## UIの変更 - なし ## 動作確認状況 - ローカルで確認 PostmanでAPI実行。 各ライセンス数値が期待通りの結果であることを確認。 ![image.png](https://dev.azure.com/ODMSCloud/6023ff7b-d41c-4fa7-9c6f-f576ba48c07c/_apis/git/repositories/302da463-a2d7-40f9-b2bb-6e8edf324fa9/pullRequests/142/attachments/image.png) ## 補足 テスト内容は、添付のテストデータを参照ください。 [テストデータ.xlsx](https://dev.azure.com/ODMSCloud/6023ff7b-d41c-4fa7-9c6f-f576ba48c07c/_apis/git/repositories/302da463-a2d7-40f9-b2bb-6e8edf324fa9/pullRequests/142/attachments/%E3%83%86%E3%82%B9%E3%83%88%E3%83%87%E3%83%BC%E3%82%BF.xlsx) --- dictation_server/src/constants/index.ts | 17 + .../features/accounts/accounts.controller.ts | 35 +-- .../accounts/accounts.service.spec.ts | 54 +--- .../src/features/accounts/accounts.service.ts | 92 +++--- .../accounts/test/accounts.service.mock.ts | 101 ++---- .../src/features/accounts/types/types.ts | 13 +- .../accounts/accounts.repository.module.ts | 3 +- .../accounts/accounts.repository.service.ts | 292 ++++++++---------- .../licenses/entity/license.entity.ts | 4 +- 9 files changed, 237 insertions(+), 374 deletions(-) diff --git a/dictation_server/src/constants/index.ts b/dictation_server/src/constants/index.ts index 12110eb..ae4070d 100644 --- a/dictation_server/src/constants/index.ts +++ b/dictation_server/src/constants/index.ts @@ -120,6 +120,23 @@ export const LICENSE_STATUS_ISSUE_REQUESTING = 'Issue Requesting'; */ export const LICENSE_STATUS_ISSUED = 'Issued'; +/** + * ライセンス状態 + * @const {string[]} + */ +export const LICENSE_ALLOCATED_STATUS = { + UNALLOCATED: 'Unallocated', + ALLOCATED: 'Allocated', + REUSABLE: 'Reusable', + DELETED: 'Deleted', +} as const; + +/** + * ライセンスの期限切れが近いと見なす日数のしきい値 + * @const {number} + */ +export const LICENSE_EXPIRATION_THRESHOLD_DAYS = 14; + /** * 音声ファイルに紐づくオプションアイテムの数 * @const {string} diff --git a/dictation_server/src/features/accounts/accounts.controller.ts b/dictation_server/src/features/accounts/accounts.controller.ts index 15e1cba..0e5e68e 100644 --- a/dictation_server/src/features/accounts/accounts.controller.ts +++ b/dictation_server/src/features/accounts/accounts.controller.ts @@ -118,40 +118,11 @@ export class AccountsController { ): Promise { console.log(req.header('Authorization')); console.log(body); - // アクセストークンにより権限を確認する - /* const pubKey = await this.cryptoService.getPublicKey(); - const accessToken = retrieveAuthorizationToken(req); - //アクセストークンが存在しない場合のエラー - if (accessToken == undefined) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const payload = verify(accessToken, pubKey); - - //アクセストークン形式エラー - if (isVerifyError(payload)) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } */ - //アクセストークンの権限不足エラー - /* if (!confirmPermission(payload.role)) { - throw new HttpException( - makeErrorResponse('E000108'), - HttpStatus.UNAUTHORIZED, - ); - } */ - /* const info = await this.accountService.getLicenseSummary( - Number(payload.userId), - ); */ - /* return { + const info = await this.accountService.getLicenseSummary(body.accountId); + return { licenseSummaryInfo: info, - }; */ - return; + }; } @ApiResponse({ diff --git a/dictation_server/src/features/accounts/accounts.service.spec.ts b/dictation_server/src/features/accounts/accounts.service.spec.ts index 5209ec1..343b6a2 100644 --- a/dictation_server/src/features/accounts/accounts.service.spec.ts +++ b/dictation_server/src/features/accounts/accounts.service.spec.ts @@ -10,7 +10,7 @@ import { describe('AccountsService', () => { it('アカウントに紐づくライセンス情報を取得する', async () => { - const user_id = 1; + const accountId = 1; const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); const adb2cParam = makeDefaultAdB2cMockValue(); const accountsRepositoryMockValue = @@ -22,17 +22,17 @@ describe('AccountsService', () => { adb2cParam, sendGridMockValue, ); - expect(await service.getLicenseSummary(user_id)).toEqual( + expect(await service.getLicenseSummary(accountId)).toEqual( expectedAccountLisenceCounts, ); }); }); it('ライセンス情報が取得できない場合、エラーとなる', async () => { - const user_id = 1; + const accountId = 1; const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); - usersRepositoryMockValue.findUserById = new Error('DB error'); const adb2cParam = makeDefaultAdB2cMockValue(); const accountsRepositoryMockValue = makeDefaultAccountsRepositoryMockValue(); + accountsRepositoryMockValue.getLicenseSummaryInfo = new Error('DB error'); const sendGridMockValue = makeDefaultSendGridlValue(); const service = await makeAccountsServiceMock( accountsRepositoryMockValue, @@ -40,49 +40,7 @@ it('ライセンス情報が取得できない場合、エラーとなる', asyn adb2cParam, sendGridMockValue, ); - await expect(service.getLicenseSummary(user_id)).rejects.toEqual( - new HttpException( - makeErrorResponse('E009999'), - HttpStatus.INTERNAL_SERVER_ERROR, - ), - ); -}); -it('アクセストークンからユーザ情報を取得する', async () => { - const token = { - userId: 'ede66c43-9b9d-4222-93ed-5f11c96e08e2', - role: 'none admin', - tier: 5, - }; - const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); - const adb2cParam = makeDefaultAdB2cMockValue(); - const accountsRepositoryMockValue = makeDefaultAccountsRepositoryMockValue(); - const sendGridMockValue = makeDefaultSendGridlValue(); - const service = await makeAccountsServiceMock( - accountsRepositoryMockValue, - usersRepositoryMockValue, - adb2cParam, - sendGridMockValue, - ); - expect(await service.getMyAccountInfo(token)).toEqual(1234567890123456); -}); -it('ユーザ情報が取得できない場合エラーとなる', async () => { - const token = { - userId: 'ede66c43-9b9d-4222-93ed-5f11c96e08e2', - role: 'none admin', - tier: 5, - }; - const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); - usersRepositoryMockValue.findUserByExternalId = new Error('DB error'); - const adb2cParam = makeDefaultAdB2cMockValue(); - const accountsRepositoryMockValue = makeDefaultAccountsRepositoryMockValue(); - const sendGridMockValue = makeDefaultSendGridlValue(); - const service = await makeAccountsServiceMock( - accountsRepositoryMockValue, - usersRepositoryMockValue, - adb2cParam, - sendGridMockValue, - ); - await expect(service.getMyAccountInfo(token)).rejects.toEqual( + await expect(service.getLicenseSummary(accountId)).rejects.toEqual( new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, @@ -98,7 +56,7 @@ const expectedAccountLisenceCounts = { expiringWithin14daysLicense: 5, issueRequesting: 6, numberOfRequesting: 7, - shortage: 8, + shortage: 3, storageSize: 0, usedSize: 0, isAccountLock: false, diff --git a/dictation_server/src/features/accounts/accounts.service.ts b/dictation_server/src/features/accounts/accounts.service.ts index 1ec54a6..1a8ee60 100644 --- a/dictation_server/src/features/accounts/accounts.service.ts +++ b/dictation_server/src/features/accounts/accounts.service.ts @@ -13,10 +13,9 @@ import { } from '../../gateways/adb2c/adb2c.service'; import { Account } from '../../repositories/accounts/entity/account.entity'; import { User } from '../../repositories/users/entity/user.entity'; -import { TIER_5 } from '../../constants'; +import { LICENSE_EXPIRATION_THRESHOLD_DAYS, TIER_5 } from '../../constants'; import { makeErrorResponse } from '../../common/error/makeErrorResponse'; import { LicenseSummaryInfo } from './types/types'; -import { User as EntityUser } from '../../repositories/users/entity/user.entity'; import { AccessToken } from '../../common/token'; @Injectable() @@ -31,58 +30,67 @@ export class AccountsService { private readonly logger = new Logger(AccountsService.name); /** * 第五階層用のライセンス情報を取得する - * @param userId + * @param accountId * @returns LicenseSummary */ - async getLicenseSummary(userId: number): Promise { + async getLicenseSummary(accountId: number): Promise { this.logger.log(`[IN] ${this.getLicenseSummary.name}`); - //DBよりアクセス者の所属するアカウントIDを取得する - let adminUser: EntityUser; - const licenseSammury = new LicenseSummaryInfo(); try { - adminUser = await this.usersRepository.findUserById(userId); - const accountId = adminUser.account_id; - // Total Licenseの取得を行う - licenseSammury.totalLicense = - await this.accountRepository.getTotalLicense(accountId); - // Allocated licenseの取得を行う - licenseSammury.allocatedLicense = - await this.accountRepository.getAllocatedLicense(accountId); - // Reusable licenseの取得を行う - licenseSammury.reusableLicense = - await this.accountRepository.getReusableLicense(accountId); - // Free licenseの取得を行う - licenseSammury.freeLicense = await this.accountRepository.getFreeLicense( - accountId, + const currentDate = new Date(); + // 有効期限との比較は時間まで見ず日付だけで判別するため、各値0をセット + currentDate.setHours(0, 0, 0, 0); + + const expiringSoonDate = new Date(currentDate.getTime()); + expiringSoonDate.setDate( + currentDate.getDate() + LICENSE_EXPIRATION_THRESHOLD_DAYS, ); - // Expiring within 14days licenseの取得を行う - licenseSammury.expiringWithin14daysLicense = - await this.accountRepository.getExpiringWithin14DaysLicense(accountId); - // Issue Requestingの取得を行う - licenseSammury.issueRequesting = - await this.accountRepository.getIssueRequesting(accountId); - // Number of Requestingの取得を行う - licenseSammury.numberOfRequesting = - await this.accountRepository.getNumberOfRequesting(accountId); - // Shortageの取得を行う - licenseSammury.shortage = await this.accountRepository.getShortage( - accountId, - ); - // XXX Storage Size、PBI1203では対象外のため0固定 - licenseSammury.storageSize = 0; - // XXX Used Size、PBI1203では対象外のため0固定 - licenseSammury.usedSize = 0; - // Account Lockの状態を取得する - licenseSammury.isAccountLock = - await this.accountRepository.getIsAccountLocked(accountId); + // システム上有効期限日付の23時59分59秒999ミリ秒までライセンスは有効なため、各値最大値をセット + expiringSoonDate.setHours(23, 59, 59, 999); + + const { licenseSummary, isAccountLock } = + await this.accountRepository.getLicenseSummaryInfo( + 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 licenseSummaryInfo: LicenseSummaryInfo = { + totalLicense, + allocatedLicense, + reusableLicense, + freeLicense, + expiringWithin14daysLicense: expiringSoonLicense, + issueRequesting, + numberOfRequesting, + storageSize: 0, // XXX PBI1201対象外 + usedSize: 0, // XXX PBI1201対象外 + shortage, + isAccountLock, + }; + return licenseSummaryInfo; } catch (e) { + console.log(e); + console.log('get licenseSummary failed'); throw new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, ); } - return licenseSammury; } /** * アカウント情報をDBに作成する diff --git a/dictation_server/src/features/accounts/test/accounts.service.mock.ts b/dictation_server/src/features/accounts/test/accounts.service.mock.ts index 997c8bf..b540be5 100644 --- a/dictation_server/src/features/accounts/test/accounts.service.mock.ts +++ b/dictation_server/src/features/accounts/test/accounts.service.mock.ts @@ -9,6 +9,7 @@ import { ConflictError, } from '../../../gateways/adb2c/adb2c.service'; import { SendGridService } from '../../../gateways/sendgrid/sendgrid.service'; +import { LicenseSummaryInfo } from '../types/types'; export type UsersRepositoryMockValue = { findUserById: User | Error; findUserByExternalId: User | Error; @@ -25,15 +26,7 @@ export type SendGridMockValue = { sendMail: undefined | Error; }; export type AccountsRepositoryMockValue = { - getTotalLicense: number | Error; - getAllocatedLicense: number | Error; - getReusableLicense: number | Error; - getFreeLicense: number | Error; - getExpiringWithin14DaysLicense: number | Error; - getIssueRequesting: number | Error; - getNumberOfRequesting: number | Error; - getShortage: number | Error; - getIsAccountLocked: boolean | Error; + getLicenseSummaryInfo: LicenseSummaryInfo | Error; }; export const makeAccountsServiceMock = async ( accountsRepositoryMockValue: AccountsRepositoryMockValue, @@ -70,74 +63,24 @@ export const makeAccountsServiceMock = async ( export const makeAccountsRepositoryMock = ( value: AccountsRepositoryMockValue, ) => { - const { - getTotalLicense, - getAllocatedLicense, - getReusableLicense, - getFreeLicense, - getExpiringWithin14DaysLicense, - getIssueRequesting, - getNumberOfRequesting, - getShortage, - getIsAccountLocked, - } = value; + const { getLicenseSummaryInfo } = value; return { - getTotalLicense: - getTotalLicense instanceof Error - ? jest.fn, []>().mockRejectedValue(getTotalLicense) - : jest.fn, []>().mockResolvedValue(getTotalLicense), - getAllocatedLicense: - getAllocatedLicense instanceof Error - ? jest.fn, []>().mockRejectedValue(getAllocatedLicense) - : jest.fn, []>().mockResolvedValue(getAllocatedLicense), - getReusableLicense: - getReusableLicense instanceof Error - ? jest.fn, []>().mockRejectedValue(getReusableLicense) - : jest.fn, []>().mockResolvedValue(getReusableLicense), - getFreeLicense: - getFreeLicense instanceof Error - ? jest.fn, []>().mockRejectedValue(getFreeLicense) - : jest.fn, []>().mockResolvedValue(getFreeLicense), - getExpiringWithin14DaysLicense: - getExpiringWithin14DaysLicense instanceof Error - ? jest - .fn, []>() - .mockRejectedValue(getExpiringWithin14DaysLicense) + getLicenseSummaryInfo: + getLicenseSummaryInfo instanceof Error + ? jest.fn, []>().mockRejectedValue(getLicenseSummaryInfo) : jest - .fn, []>() - .mockResolvedValue(getExpiringWithin14DaysLicense), - getIssueRequesting: - getIssueRequesting instanceof Error - ? jest.fn, []>().mockRejectedValue(getIssueRequesting) - : jest.fn, []>().mockResolvedValue(getIssueRequesting), - getNumberOfRequesting: - getNumberOfRequesting instanceof Error - ? jest.fn, []>().mockRejectedValue(getNumberOfRequesting) - : jest - .fn, []>() - .mockResolvedValue(getNumberOfRequesting), - getShortage: - getShortage instanceof Error - ? jest.fn, []>().mockRejectedValue(getShortage) - : jest.fn, []>().mockResolvedValue(getShortage), - getIsAccountLocked: - getIsAccountLocked instanceof Error - ? jest.fn, []>().mockRejectedValue(getIsAccountLocked) - : jest.fn, []>().mockResolvedValue(getIsAccountLocked), + .fn, []>() + .mockResolvedValue(getLicenseSummaryInfo), }; }; export const makeUsersRepositoryMock = (value: UsersRepositoryMockValue) => { - const { findUserById, findUserByExternalId } = value; + const { findUserById } = value; return { findUserById: findUserById instanceof Error ? jest.fn, []>().mockRejectedValue(findUserById) : jest.fn, []>().mockResolvedValue(findUserById), - findUserByExternalId: - findUserByExternalId instanceof Error - ? jest.fn, []>().mockRejectedValue(findUserByExternalId) - : jest.fn, []>().mockResolvedValue(findUserByExternalId), }; }; export const makeAdB2cServiceMock = (value: AdB2cMockValue) => { @@ -172,17 +115,21 @@ export const makeSendGridServiceMock = (value: SendGridMockValue) => { // 個別のテストケースに対応してそれぞれのMockを用意するのは無駄が多いのでテストケース内で個別の値を設定する export const makeDefaultAccountsRepositoryMockValue = (): AccountsRepositoryMockValue => { - return { - getTotalLicense: 1, - getAllocatedLicense: 2, - getReusableLicense: 3, - getFreeLicense: 4, - getExpiringWithin14DaysLicense: 5, - getIssueRequesting: 6, - getNumberOfRequesting: 7, - getShortage: 8, - getIsAccountLocked: false, + let licenseSummaryInfo = new LicenseSummaryInfo(); + licenseSummaryInfo = { + totalLicense: 1, + allocatedLicense: 2, + reusableLicense: 3, + freeLicense: 4, + expiringWithin14daysLicense: 5, + issueRequesting: 6, + numberOfRequesting: 7, + shortage: 8, + storageSize: 0, + usedSize: 0, + isAccountLock: false, }; + return { getLicenseSummaryInfo: licenseSummaryInfo }; }; export const makeDefaultUsersRepositoryMockValue = (): UsersRepositoryMockValue => { @@ -199,7 +146,7 @@ export const makeDefaultUsersRepositoryMockValue = user.notification = false; user.deleted_at = null; user.created_by = 'test'; - user.created_at = new Date('2023-06-13 00:00:00'); + user.created_at = new Date(); user.updated_by = null; user.updated_at = null; diff --git a/dictation_server/src/features/accounts/types/types.ts b/dictation_server/src/features/accounts/types/types.ts index 642e391..de138dd 100644 --- a/dictation_server/src/features/accounts/types/types.ts +++ b/dictation_server/src/features/accounts/types/types.ts @@ -33,7 +33,7 @@ export class GetLicenseSummaryRequest { @ApiProperty() accountId: number; } - +// XXX Task1961で直す、レスポンス内に直接定義する export class LicenseSummaryInfo { @ApiProperty() totalLicense: number; @@ -68,6 +68,17 @@ export class LicenseSummaryInfo { @ApiProperty() isAccountLock: boolean; } +// XXX Task1961で直すLicenseSummaryInfo2→LicenseSummaryInfo +export class LicenseSummaryInfo2 { + totalLicense: number; + allocatedLicense: number; + reusableLicense: number; + freeLicense: number; + expiringSoonLicense: number; + issueRequesting: number; + numberOfRequesting: number; + allocatableLicenseWithMargin: number; +} export class GetLicenseSummaryResponse { @ApiProperty({ type: LicenseSummaryInfo }) licenseSummaryInfo: LicenseSummaryInfo; diff --git a/dictation_server/src/repositories/accounts/accounts.repository.module.ts b/dictation_server/src/repositories/accounts/accounts.repository.module.ts index ddd0efd..999d78a 100644 --- a/dictation_server/src/repositories/accounts/accounts.repository.module.ts +++ b/dictation_server/src/repositories/accounts/accounts.repository.module.ts @@ -2,9 +2,10 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Account } from './entity/account.entity'; import { AccountsRepositoryService } from './accounts.repository.service'; +import { License, LicenseOrder } from '../licenses/entity/license.entity'; @Module({ - imports: [TypeOrmModule.forFeature([Account])], + imports: [TypeOrmModule.forFeature([Account, License, LicenseOrder])], providers: [AccountsRepositoryService], exports: [AccountsRepositoryService], }) diff --git a/dictation_server/src/repositories/accounts/accounts.repository.service.ts b/dictation_server/src/repositories/accounts/accounts.repository.service.ts index dc7fae6..2ce3c01 100644 --- a/dictation_server/src/repositories/accounts/accounts.repository.service.ts +++ b/dictation_server/src/repositories/accounts/accounts.repository.service.ts @@ -2,8 +2,10 @@ import { Injectable } from '@nestjs/common'; import { Between, DataSource, + In, IsNull, MoreThan, + MoreThanOrEqual, Not, UpdateResult, } from 'typeorm'; @@ -15,7 +17,11 @@ import { getDirection, getTaskListSortableAttribute, } from '../../common/types/sort/util'; -import { LICENSE_STATUS_ISSUE_REQUESTING } from '../../constants'; +import { + LICENSE_ALLOCATED_STATUS, + LICENSE_STATUS_ISSUE_REQUESTING, +} from '../../constants'; +import { LicenseSummaryInfo2 } from '../../features/accounts/types/types'; export class AccountNotFoundError extends Error {} @@ -157,186 +163,130 @@ export class AccountsRepositoryService { } return account; } + /** - * アカウントIDから有効な総ライセンス数を取得する + * アカウントIDからライセンス情報を取得する * @param id - * @returns count + * @param currentDate + * @param expiringSoonDate + * @returns licenseSummary */ - async getTotalLicense(id: number): Promise { - const count = await this.dataSource.getRepository(License).count({ - where: [ - { account_id: id, expiry_date: MoreThan(new Date()) }, - { account_id: id, expiry_date: IsNull() }, - ], - }); - return count; - } - /** - * アカウントIDから有効な総ライセンス数のうち、ユーザーに割り当て済みのライセンス数を取得する - * @param id - * @returns count - */ - async getAllocatedLicense(id: number): Promise { - const count = await this.dataSource.getRepository(License).count({ - where: { - account_id: id, - type: Not(IsNull()), - }, - }); - return count; - } - /** - * アカウントIDから総ライセンス数のうち、 - * ユーザーに割り当てたことがあるが、 - * 現在は割り当て解除され誰にも割り当たっていないライセンス数を取得する - * @param id - * @returns count - */ - async getReusableLicense(id: number): Promise { - const count = await this.dataSource - .getRepository(License) - .createQueryBuilder('License') - .leftJoinAndSelect( - 'lisence.lisence_history', - 'history', - 'license.id=lisence_history.license_id', - ) - .where('lisence.account_id = :id', { id }) - .andWhere('history.id IS NOT NULL') - // XXX Unallocated(未割当)は仮、値が決定したら修正する - .andWhere('license.status = :status', { status: 'Unallocated' }) - .getCount(); - return count; - } - /** - * アカウントIDから総ライセンス数のうち、 - * 一度もユーザーに割り当てたことのないライセンス数を取得する - * @param id - * @returns count - */ - async getFreeLicense(id: number): Promise { - const count = await this.dataSource - .getRepository(License) - .createQueryBuilder('License') - .leftJoinAndSelect( - 'lisence.lisence_history', - 'history', - 'license.id=lisence_history.license_id', - ) - .where('lisence.account_id = :id', { id }) - .andWhere('history.id IS NULL') - .getCount(); - return count; - } - /** - * アカウントIDから総ライセンス数のうち - * 、有効期限が現在日付より14日以内のライセンス数を取得する(割り当て状態は無関係) - * @param id - * @returns count - */ - async getExpiringWithin14DaysLicense(id: number): Promise { - const currentDate = new Date(); - const fourteenDaysLater = new Date(); - fourteenDaysLater.setDate(fourteenDaysLater.getDate() + 14); - const options = { - where: [ - { + // XXX Task1961で直すLicenseSummaryInfo2→LicenseSummaryInfo + async getLicenseSummaryInfo( + id: number, + currentDate: Date, + expiringSoonDate: Date, + ): Promise<{ licenseSummary: LicenseSummaryInfo2; isAccountLock: boolean }> { + return await this.dataSource.transaction(async (entityManager) => { + const license = entityManager.getRepository(License); + const licenseOrder = entityManager.getRepository(LicenseOrder); + + // 有効な総ライセンス数を取得する + const totalLicense = await license.count({ + where: [ + { + account_id: id, + expiry_date: MoreThanOrEqual(currentDate), + status: Not(LICENSE_ALLOCATED_STATUS.DELETED), + }, + { + account_id: id, + expiry_date: IsNull(), + status: Not(LICENSE_ALLOCATED_STATUS.DELETED), + }, + ], + }); + + // 有効な総ライセンス数のうち、ユーザーに割り当て済みのライセンス数を取得する + const allocatedLicense = await license.count({ + where: { account_id: id, - expiry_date: Between(currentDate, fourteenDaysLater), + allocated_user_id: Not(IsNull()), + status: LICENSE_ALLOCATED_STATUS.ALLOCATED, }, - { account_id: id, expiry_date: Not(IsNull()) }, - ], - }; + }); - const count = await this.dataSource.getRepository(License).count(options); + // 総ライセンス数のうち、ユーザーに割り当てたことがあるが、現在は割り当て解除され誰にも割り当たっていないライセンス数を取得する + const reusableLicense = await license.count({ + where: { + account_id: id, + status: LICENSE_ALLOCATED_STATUS.REUSABLE, + }, + }); - return count; - } - /** - * アカウントIDから未発行状態あるいは発行キャンセルされた注文数を取得する - * @param id - * @returns count - */ - async getIssueRequesting(id: number): Promise { - const count = await this.dataSource.getRepository(LicenseOrder).count({ - where: [ - { + // 総ライセンス数のうち、一度もユーザーに割り当てたことのないライセンス数を取得する + const freeLicense = await license.count({ + where: { + account_id: id, + status: LICENSE_ALLOCATED_STATUS.UNALLOCATED, + }, + }); + + // 有効期限が現在日付からしきい値以内のライセンス数を取得する + const expiringSoonLicense = await license.count({ + where: { + account_id: id, + expiry_date: Between(currentDate, expiringSoonDate), + status: Not(LICENSE_ALLOCATED_STATUS.DELETED), + }, + }); + + // 未発行状態あるいは発行キャンセルされた注文数を取得する + const issueRequesting = await licenseOrder.count({ + where: { from_account_id: id, status: LICENSE_STATUS_ISSUE_REQUESTING, }, - ], + }); + + // 未発行状態あるいは発行キャンセルされた注文の総ライセンス数を取得する + const result = await licenseOrder + .createQueryBuilder('license_orders') + .select('SUM(license_orders.quantity)', 'sum') + .where('license_orders.from_account_id = :id', { id }) + .andWhere('license_orders.status = :status', { + status: LICENSE_STATUS_ISSUE_REQUESTING, + }) + .getRawOne(); + const numberOfRequesting = parseInt(result.sum, 10) || 0; + + // 有効期限がしきい値より未来または未設定で、割り当て可能なライセンス数の取得を行う + const allocatableLicenseWithMargin = await license.count({ + where: [ + { + account_id: id, + status: In([ + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + LICENSE_ALLOCATED_STATUS.REUSABLE, + ]), + expiry_date: MoreThan(expiringSoonDate), + }, + { + account_id: id, + status: In([ + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + LICENSE_ALLOCATED_STATUS.REUSABLE, + ]), + expiry_date: IsNull(), + }, + ], + }); + + // アカウントのロック状態を取得する + const isAccountLock = (await this.findAccountById(id)).locked; + + let licenseSummary = new LicenseSummaryInfo2(); + licenseSummary = { + totalLicense: totalLicense, + allocatedLicense: allocatedLicense, + reusableLicense: reusableLicense, + freeLicense: freeLicense, + expiringSoonLicense: expiringSoonLicense, + allocatableLicenseWithMargin: allocatableLicenseWithMargin, + issueRequesting: issueRequesting, + numberOfRequesting: numberOfRequesting, + }; + return { licenseSummary: licenseSummary, isAccountLock }; }); - - return count; - } - /** - * アカウントIDから未発行状態あるいは発行キャンセルされた注文の総ライセンス数を取得する - * @param id - * @returns count - */ - async getNumberOfRequesting(id: number): Promise { - const result = await this.dataSource - .getRepository(LicenseOrder) - .createQueryBuilder() - .select('SUM(license_orders.quantity)', 'sum') - .where('license_orders.from_account_id = :id', { id }) - .andWhere('license_orders.status = :status', { - status: LICENSE_STATUS_ISSUE_REQUESTING, - }) - .getRawOne(); - - const sum = parseInt(result.sum, 10) || 0; - - return sum; - } - /** - * アカウントIDから不足数( - * {有効期限が15日以上または未設定の、未割当または割り当て解除済みライセンス数} - {有効期限が14日以内のライセンス数} - * が負の値の場合、その絶対値。なお、0以上の場合は0)の取得を行う - * @param id - * @returns count - */ - async getShortage(id: number): Promise { - const fifteenDaysLater = new Date(); - fifteenDaysLater.setDate(fifteenDaysLater.getDate() + 15); - - const options = { - where: [ - { - status: 'Unallocated', - account_id: id, - expiry_date: MoreThan(fifteenDaysLater), - }, - { status: 'Unallocated', account_id: id, expiry_date: Not(IsNull()) }, - ], - }; - - const shortageCount = await this.dataSource - .getRepository(License) - .count(options); - const expiringWithin14DaysCount = await this.getExpiringWithin14DaysLicense( - id, - ); - - let result = shortageCount - expiringWithin14DaysCount; - result = Math.abs(result); // 絶対値を取得 - - return result >= 0 ? result : 0; - } - /** - * アカウントIDからアカウントのロック状態を取得する - * @param id - * @returns isLock - */ - async getIsAccountLocked(id: number): Promise { - const result = await this.dataSource.getRepository(Account).findOne({ - where: [ - { - id: id, - }, - ], - }); - - return result.locked; } } diff --git a/dictation_server/src/repositories/licenses/entity/license.entity.ts b/dictation_server/src/repositories/licenses/entity/license.entity.ts index dfaf7b0..84de72a 100644 --- a/dictation_server/src/repositories/licenses/entity/license.entity.ts +++ b/dictation_server/src/repositories/licenses/entity/license.entity.ts @@ -35,7 +35,7 @@ export class LicenseOrder { canceled_at?: Date; } -@Entity({ name: 'license' }) +@Entity({ name: 'licenses' }) export class License { @PrimaryGeneratedColumn() id: number; @@ -64,7 +64,7 @@ export class License { @Column() delete_order_id: number; } -@Entity({ name: 'license_history' }) +@Entity({ name: 'licenses_history' }) export class LicenseHistory { @PrimaryGeneratedColumn() id: number;