From 98e9937d9d6eaad0a6822327876a10b2b75043a9 Mon Sep 17 00:00:00 2001 From: "oura.a" Date: Tue, 8 Aug 2023 08:11:56 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20313:=20=E7=AC=AC=E4=BA=94?= =?UTF-8?q?=E9=9A=8E=E5=B1=A4=E3=81=AEShortage=E3=81=AB=E3=81=A4=E3=81=84?= =?UTF-8?q?=E3=81=A6=E6=95=B0=E5=80=A4=E3=82=92licensesummary=E3=81=AE?= =?UTF-8?q?=E3=82=82=E3=81=AE=E3=81=A8=E5=90=8C=E3=81=98=E3=81=AB=E3=81=99?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2283: 第五階層のShortageについて数値をlicensesummaryのものと同じにする](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2283) 以下のプルリクで受けた指摘点を修正しました。 https://dev.azure.com/ODMSCloud/ODMS%20Cloud/_git/ODMS%20Cloud/pullrequest/273 具体的な修正内容は以下になります。 ・子アカウントの数分だけDBクエリしにいく問題を修正 ・有効期限の閾値となる日時を算出するclassを追加 ## レビューポイント ご指摘いただいた問題がこの対応で解消されているか。 ## UIの変更 なし ## 動作確認状況 ローカルで動作確認済み ## 補足 なし --- .../accounts/accounts.service.spec.ts | 148 +++++++++++++++++- .../src/features/accounts/accounts.service.ts | 38 ++--- .../src/features/accounts/test/utility.ts | 24 +++ .../src/features/accounts/types/types.ts | 10 +- .../src/features/licenses/types/types.ts | 14 ++ .../accounts/accounts.repository.service.ts | 73 ++++----- 6 files changed, 236 insertions(+), 71 deletions(-) diff --git a/dictation_server/src/features/accounts/accounts.service.spec.ts b/dictation_server/src/features/accounts/accounts.service.spec.ts index c4925e8..7ef47f1 100644 --- a/dictation_server/src/features/accounts/accounts.service.spec.ts +++ b/dictation_server/src/features/accounts/accounts.service.spec.ts @@ -14,6 +14,7 @@ import { createAccount, createLicense, createLicenseOrder, + createLicenseSetExpiryDateAndStatus, } from './test/utility'; import { DataSource } from 'typeorm'; import { makeTestingModule } from '../../common/test/modules'; @@ -346,7 +347,7 @@ const expectedAccountLisenceCounts = { isStorageAvailable: false, }; -describe('createPartnerAccount', () => { +describe('getPartnerAccount', () => { let source: DataSource = null; beforeEach(async () => { source = new DataSource({ @@ -387,6 +388,20 @@ describe('createPartnerAccount', () => { 'CHILDCORP2', ); + // 第二にリクエストを投げる用の第三を作成 + const { accountId: childAccountId3 } = await createAccount( + source, + childAccountId1, + 3, + 'CHILDCORP3', + ); + const { accountId: childAccountId4 } = await createAccount( + source, + childAccountId2, + 3, + 'CHILDCORP4', + ); + // 所有ライセンスを追加(親:3、子1:1、子2:2) await createLicense(source, parentAccountId); await createLicense(source, parentAccountId); @@ -407,6 +422,10 @@ describe('createPartnerAccount', () => { ); await createLicenseOrder(source, childAccountId2, parentAccountId, 5); + // ライセンス注文を追加(子3→子1:10ライセンス、子4→子2:10ライセンス) + await createLicenseOrder(source, childAccountId3, childAccountId1, 10); + await createLicenseOrder(source, childAccountId4, childAccountId2, 10); + const service = module.get(AccountsService); const accountId = parentAccountId; const offset = 0; @@ -426,14 +445,139 @@ describe('createPartnerAccount', () => { expect(response.childrenPartnerLicenses[0].tier).toBe(2); expect(response.childrenPartnerLicenses[0].stockLicense).toBe(1); expect(response.childrenPartnerLicenses[0].issueRequesting).toBe(10); + expect(response.childrenPartnerLicenses[0].shortage).toBe(9); expect(response.childrenPartnerLicenses[1].companyName).toBe('CHILDCORP2'); expect(response.childrenPartnerLicenses[1].tier).toBe(2); expect(response.childrenPartnerLicenses[1].stockLicense).toBe(2); - expect(response.childrenPartnerLicenses[1].issueRequesting).toBe(5); + expect(response.childrenPartnerLicenses[1].shortage).toBe(8); }); }); +describe('getPartnerAccount', () => { + 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('パラメータのアカウント自身と子アカウントに紐つくライセンス情報を取得する(第五のshortage確認)', async () => { + const module = await makeTestingModule(source); + + // 親アカウントと子アカウント2つ作成 + const { accountId: parentAccountId } = await createAccount( + source, + 0, + 4, + 'PARENTCORP', + ); + const { accountId: childAccountId1 } = await createAccount( + source, + parentAccountId, + 5, + 'CHILDCORP1', + ); + const { accountId: childAccountId2 } = await createAccount( + source, + parentAccountId, + 5, + 'CHILDCORP2', + ); + const { accountId: childAccountId3 } = await createAccount( + source, + parentAccountId, + 5, + 'CHILDCORP3', + ); + + // 有効期限が14日後のライセンスを追加(5ライセンス) + let expiryDate = new Date(); + expiryDate.setDate(expiryDate.getDate() + 14); + expiryDate.setHours(23, 59, 59, 999); + for (let i = 0; i < 5; i++) { + await createLicenseSetExpiryDateAndStatus( + source, + childAccountId1, + expiryDate, + 'Allocated', + ); + await createLicenseSetExpiryDateAndStatus( + source, + childAccountId2, + expiryDate, + 'Allocated', + ); + } + + // 有効期限が15日後のライセンスを追加 + expiryDate.setDate(expiryDate.getDate() + 15); + expiryDate.setHours(23, 59, 59, 999); + await createLicenseSetExpiryDateAndStatus( + source, + childAccountId3, + expiryDate, + 'Allocated', + ); + + // 有効期限が迫っていないライセンスを追加(子1:各ステータスのライセンスを1つずつ、計4つ) + const status = ['Unallocated', 'Allocated', 'Reusable', 'Deleted']; + status.forEach(async (element) => { + await createLicenseSetExpiryDateAndStatus( + source, + childAccountId1, + new Date(2500, 1, 1, 23, 59, 59), + element, + ); + }); + + // 有効期限が迫っていないライセンスを追加(子2:Unallocatedを10件) + for (let i = 0; i < 10; i++) { + await createLicenseSetExpiryDateAndStatus( + source, + childAccountId2, + new Date(2500, 1, 1, 23, 59, 59), + 'Unallocated', + ); + } + + // 有効期限未設定のライセンスを1件追加(子1) + await createLicense(source, childAccountId1); + + const service = module.get(AccountsService); + const accountId = parentAccountId; + const offset = 0; + const limit = 20; + + const response = await service.getPartnerLicenses(limit, offset, accountId); + + // 有効期限間近(5件)+ 有効期限間近でない(3件)'Unallocated', 'Allocated', 'Reusable'+ 有効期限未設定(1件) → 9件 + expect(response.childrenPartnerLicenses[0].stockLicense).toBe(9); + // 有効期限間近(5件) - {有効期限間近でない未割当(2件)'Unallocated', 'Reusable'+ 有効期限未設定(1件)} → 2件 + expect(response.childrenPartnerLicenses[0].shortage).toBe(2); + + // 有効期限間近(5件)+ 有効期限間近でない(10件) → 15件 + expect(response.childrenPartnerLicenses[1].stockLicense).toBe(15); + // 有効期限間近(5件)- (有効期限間近でない未割当(10件)'Unallocated' + 有効期限未設定(1件)) → -5件 → 0件 + expect(response.childrenPartnerLicenses[1].shortage).toBe(0); + + expect(response.childrenPartnerLicenses[2].stockLicense).toBe(1); + // 有効期限が15日後のものはshortageにカウントされない + expect(response.childrenPartnerLicenses[2].shortage).toBe(0); + }); +}); + + describe('getOrderHistories', () => { let source: DataSource = null; beforeEach(async () => { diff --git a/dictation_server/src/features/accounts/accounts.service.ts b/dictation_server/src/features/accounts/accounts.service.ts index 9995b94..8db4527 100644 --- a/dictation_server/src/features/accounts/accounts.service.ts +++ b/dictation_server/src/features/accounts/accounts.service.ts @@ -26,7 +26,10 @@ import { Dealer, GetMyAccountResponse, } from './types/types'; -import { DateWithZeroTime } from '../licenses/types/types'; +import { + DateWithZeroTime, + ExpirationThresholdDate, +} from '../licenses/types/types'; import { GetLicenseSummaryResponse, Typist } from './types/types'; import { AccessToken } from '../../common/token'; import { UserNotFoundError } from '../../repositories/users/errors/types'; @@ -58,13 +61,9 @@ export class AccountsService { try { const currentDate = new DateWithZeroTime(); - - const expiringSoonDate = new Date(currentDate.getTime()); - expiringSoonDate.setDate( - currentDate.getDate() + LICENSE_EXPIRATION_THRESHOLD_DAYS, + const expiringSoonDate = new ExpirationThresholdDate( + currentDate.getTime(), ); - // システム上有効期限日付の23時59分59秒999ミリ秒までライセンスは有効なため、各値最大値をセット - expiringSoonDate.setHours(23, 59, 59, 999); const { licenseSummary, isStorageAvailable } = await this.accountRepository.getLicenseSummaryInfo( @@ -446,11 +445,17 @@ export class AccountsService { try { const currentDate = new DateWithZeroTime(); + // 第五階層のshortage算出に使用する日付情報 + // 「有効期限が現在日付からしきい値以内のライセンス数」を取得するため、しきい値となる日付を作成する + const expiringSoonDate = new ExpirationThresholdDate( + currentDate.getTime(), + ); const getPartnerLicenseResult = await this.accountRepository.getPartnerLicense( accountId, currentDate, + expiringSoonDate, offset, limit, ); @@ -471,27 +476,14 @@ export class AccountsService { }, ); - // 第五階層のshortage算出に使用する日付情報 - // 「有効期限が現在日付からしきい値以内のライセンス数」を取得するため、しきい値となる日付を作成する - const expiringSoonDate = new Date(currentDate.getTime()); - expiringSoonDate.setDate( - currentDate.getDate() + LICENSE_EXPIRATION_THRESHOLD_DAYS, - ); - expiringSoonDate.setHours(23, 59, 59, 999); - // 各子アカウントのShortageを算出してreturn用の変数にマージする const childrenPartnerLicenses: PartnerLicenseInfo[] = []; for (const childPartnerLicenseFromRepository of getPartnerLicenseResult.childPartnerLicensesFromRepository) { let childShortage; if (childPartnerLicenseFromRepository.tier === TIERS.TIER5) { - // 第五階層の場合計算式が異なるため、別途値を取得する - const { expiringSoonLicense, allocatableLicenseWithMargin } = - await this.accountRepository.getLicenseCountForShortage( - childPartnerLicenseFromRepository.accountId, - currentDate, - expiringSoonDate, - ); - childShortage = allocatableLicenseWithMargin - expiringSoonLicense; + childShortage = + childPartnerLicenseFromRepository.allocatableLicenseWithMargin - + childPartnerLicenseFromRepository.expiringSoonLicense; } else { childShortage = childPartnerLicenseFromRepository.stockLicense - diff --git a/dictation_server/src/features/accounts/test/utility.ts b/dictation_server/src/features/accounts/test/utility.ts index a098ee7..c570a4b 100644 --- a/dictation_server/src/features/accounts/test/utility.ts +++ b/dictation_server/src/features/accounts/test/utility.ts @@ -50,6 +50,30 @@ export const createLicense = async ( identifiers.pop() as License; }; +// 有効期限・ステータス付きのライセンスを作成 +export const createLicenseSetExpiryDateAndStatus = async ( + datasource: DataSource, + accountId: number, + expiryDate: Date, + status: string, +): Promise => { + const { identifiers } = await datasource.getRepository(License).insert({ + expiry_date: expiryDate, + account_id: accountId, + type: 'NORMAL', + status: status, + allocated_user_id: null, + order_id: null, + deleted_at: null, + delete_order_id: null, + created_by: 'test_runner', + created_at: new Date(), + updated_by: 'updater', + updated_at: new Date(), + }); + identifiers.pop() as License; +}; + export const createLicenseOrder = async ( datasource: DataSource, fromAccountId: number, diff --git a/dictation_server/src/features/accounts/types/types.ts b/dictation_server/src/features/accounts/types/types.ts index 0f4baf7..f62c9a0 100644 --- a/dictation_server/src/features/accounts/types/types.ts +++ b/dictation_server/src/features/accounts/types/types.ts @@ -189,11 +189,17 @@ export class GetPartnerLicensesResponse { childrenPartnerLicenses: PartnerLicenseInfo[]; } +// 第五階層のshortage算出用 +export class PartnerLicenseInfoForShortage { + expiringSoonLicense?:number; + allocatableLicenseWithMargin?:number; +} + // RepositoryからPartnerLicenseInfoに関する情報を取得する際の型 export type PartnerLicenseInfoForRepository = Omit< - PartnerLicenseInfo, + PartnerLicenseInfo & PartnerLicenseInfoForShortage, 'shortage' ->; + >; export class GetOrderHistoriesRequest { @ApiProperty({ description: '取得件数' }) diff --git a/dictation_server/src/features/licenses/types/types.ts b/dictation_server/src/features/licenses/types/types.ts index a4df377..ad6b933 100644 --- a/dictation_server/src/features/licenses/types/types.ts +++ b/dictation_server/src/features/licenses/types/types.ts @@ -1,5 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsInt, Matches, Max, Min, Length } from 'class-validator'; +import { LICENSE_EXPIRATION_THRESHOLD_DAYS } from '../../../constants'; export class CreateOrdersRequest { @ApiProperty() @@ -48,3 +49,16 @@ export class DateWithZeroTime extends Date { this.setHours(0, 0, 0, 0); // 時分秒を"0:00:00.000"に固定 } } + +// ライセンスの算出用に、閾値となる時刻(23:59:59.999)の日付を取得する +export class ExpirationThresholdDate extends Date { + constructor(...args: any[]) { + if (args.length === 0) { + super(); // 引数がない場合、現在の日付で初期化 + } else { + super(...(args as [string])); // 引数がある場合、引数をそのままDateクラスのコンストラクタに渡す + } + this.setDate(this.getDate() + LICENSE_EXPIRATION_THRESHOLD_DAYS); + this.setHours(23, 59, 59, 999); // 時分秒を"23:59:59.999"に固定 + } +} \ No newline at end of file diff --git a/dictation_server/src/repositories/accounts/accounts.repository.service.ts b/dictation_server/src/repositories/accounts/accounts.repository.service.ts index 2aabfff..f7ecd96 100644 --- a/dictation_server/src/repositories/accounts/accounts.repository.service.ts +++ b/dictation_server/src/repositories/accounts/accounts.repository.service.ts @@ -429,6 +429,7 @@ export class AccountsRepositoryService { async getPartnerLicense( id: number, currentDate: Date, + expiringSoonDate: Date, offset: number, limit: number, ): Promise<{ @@ -486,16 +487,36 @@ export class AccountsRepositoryService { entityManager, ); + // 第五の不足数を算出するためのライセンス数情報を取得する + let expiringSoonLicense: number; + let allocatableLicenseWithMargin: number; + if (childAccount.tier === TIERS.TIER5) { + expiringSoonLicense = await this.getExpiringSoonLicense( + entityManager, + childAccount.id, + currentDate, + expiringSoonDate, + ); + allocatableLicenseWithMargin = + await this.getAllocatableLicenseWithMargin( + entityManager, + childAccount.id, + expiringSoonDate, + ); + } + // 戻り値用の値を設定 const childPartnerLicenseFromRepository: PartnerLicenseInfoForRepository = - { - accountId: childAccount.id, - tier: childAccount.tier, - companyName: childAccount.company_name, - stockLicense: childLicenseOrderStatus.stockLicense, - issuedRequested: childLicenseOrderStatus.issuedRequested, - issueRequesting: childLicenseOrderStatus.issueRequesting, - }; + { + 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, @@ -517,42 +538,6 @@ export class AccountsRepositoryService { }); } - /** - * 不足数を算出するためのライセンス数情報を取得する - * @param id - * @param currentDate - * @param expiringSoonDate - * @returns expiringSoonLicense - * @returns expiringSoonDate - */ - async getLicenseCountForShortage( - id: number, - currentDate: Date, - expiringSoonDate: Date, - ): Promise<{ - expiringSoonLicense: number; - allocatableLicenseWithMargin: number; - }> { - return await this.dataSource.transaction(async (entityManager) => { - const expiringSoonLicense = await this.getExpiringSoonLicense( - entityManager, - id, - currentDate, - expiringSoonDate, - ); - const allocatableLicenseWithMargin = - await this.getAllocatableLicenseWithMargin( - entityManager, - id, - expiringSoonDate, - ); - return { - expiringSoonLicense: expiringSoonLicense, - allocatableLicenseWithMargin: allocatableLicenseWithMargin, - }; - }); - } - /** * Dealer(Tier4)アカウント情報を取得する * @returns dealer accounts