diff --git a/dictation_server/src/features/licenses/licenses.service.spec.ts b/dictation_server/src/features/licenses/licenses.service.spec.ts index 72ac6ea..37912c3 100644 --- a/dictation_server/src/features/licenses/licenses.service.spec.ts +++ b/dictation_server/src/features/licenses/licenses.service.spec.ts @@ -25,6 +25,7 @@ import { } from '../../constants'; import { makeHierarchicalAccounts, + makeTestAccount, makeTestSimpleAccount, makeTestUser, } from '../../common/test/utility'; @@ -387,8 +388,12 @@ describe('カードライセンスを取り込む', () => { if (!source) fail(); const module = await makeTestingModule(source); if (!module) fail(); + // 明日の日付を取得 + // ミリ秒以下は切り捨てる + const tommorow = new Date(); + tommorow.setDate(tommorow.getDate() + 1); + tommorow.setMilliseconds(0); - const now = new Date(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId } = await makeTestUser(source, { account_id: accountId, @@ -402,7 +407,7 @@ describe('カードライセンスを取り込む', () => { await createLicense( source, 1, - new Date(now.getTime() + 60 * 60 * 1000), + new Date(tommorow.getTime() + 60 * 60 * 1000), accountId, LICENSE_TYPE.NORMAL, LICENSE_ALLOCATED_STATUS.UNALLOCATED, @@ -428,7 +433,7 @@ describe('カードライセンスを取り込む', () => { await createLicense( source, 3, - new Date(now.getTime() + 60 * 60 * 1000), + new Date(tommorow.getTime() + 60 * 60 * 1000), accountId, LICENSE_TYPE.NORMAL, LICENSE_ALLOCATED_STATUS.UNALLOCATED, @@ -441,7 +446,7 @@ describe('カードライセンスを取り込む', () => { await createLicense( source, 4, - new Date(now.getTime() + 60 * 60 * 1000 * 2), + new Date(tommorow.getTime() + 60 * 60 * 1000 * 2), accountId, LICENSE_TYPE.NORMAL, LICENSE_ALLOCATED_STATUS.UNALLOCATED, @@ -467,7 +472,7 @@ describe('カードライセンスを取り込む', () => { await createLicense( source, 6, - new Date(now.getTime() + 60 * 60 * 1000 * 2), + new Date(tommorow.getTime() + 60 * 60 * 1000 * 2), accountId, LICENSE_TYPE.NORMAL, LICENSE_ALLOCATED_STATUS.ALLOCATED, @@ -480,7 +485,7 @@ describe('カードライセンスを取り込む', () => { await createLicense( source, 7, - new Date(now.getTime() + 60 * 60 * 1000 * 2), + new Date(tommorow.getTime() + 60 * 60 * 1000 * 2), accountId, LICENSE_TYPE.NORMAL, LICENSE_ALLOCATED_STATUS.DELETED, @@ -493,7 +498,7 @@ describe('カードライセンスを取り込む', () => { await createLicense( source, 8, - new Date(now.getTime() + 60 * 60 * 1000), + new Date(tommorow.getTime() + 60 * 60 * 1000), accountId + 1, LICENSE_TYPE.NORMAL, LICENSE_ALLOCATED_STATUS.UNALLOCATED, @@ -506,7 +511,7 @@ describe('カードライセンスを取り込む', () => { await createLicense( source, 9, - new Date(now.getTime() - 60 * 60 * 1000 * 24), + new Date(tommorow.getTime() - 60 * 60 * 1000 * 24), accountId, LICENSE_TYPE.NORMAL, LICENSE_ALLOCATED_STATUS.UNALLOCATED, @@ -1610,3 +1615,397 @@ describe('ライセンス注文キャンセル', () => { ); }); }); + +describe('割り当て可能なライセンス取得', () => { + let source: DataSource | null = null; + beforeAll(async () => { + if (source == null) { + source = await (async () => { + const s = new DataSource({ + type: 'mysql', + host: 'test_mysql_db', + port: 3306, + username: 'user', + password: 'password', + database: 'odms', + entities: [__dirname + '/../../**/*.entity{.ts,.js}'], + synchronize: false, // trueにすると自動的にmigrationが行われるため注意 + logger: new TestLogger('none'), + logging: true, + }); + return await s.initialize(); + })(); + } + }); + + beforeEach(async () => { + if (source) { + await truncateAllTable(source); + } + }); + + afterAll(async () => { + await source?.destroy(); + source = null; + }); + + it('割り当て可能なライセンスを取得できる', async () => { + if (!source) fail(); + const module = await makeTestingModule(source); + if (!module) fail(); + const { account, admin } = await makeTestAccount(source, { + company_name: 'AAA', + tier: 5, + }); + // ライセンスを作成 + // 有効期限が当日のライセンスを1つ、有効期限が翌日のライセンスを1つ、有効期限が翌々日のライセンスを1つ作成 + // ミリ秒以下は切り捨てる DBで扱っていないため + const today = new Date(); + today.setMilliseconds(0); + const tomorrow = new Date(); + tomorrow.setDate(tomorrow.getDate() + 1); + tomorrow.setMilliseconds(0); + const dayAfterTomorrow = new Date(); + dayAfterTomorrow.setDate(dayAfterTomorrow.getDate() + 2); + dayAfterTomorrow.setMilliseconds(0); + await createLicense( + source, + 1, + today, + account.id, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + null, + null, + null, + null, + ); + await createLicense( + source, + 2, + tomorrow, + account.id, + LICENSE_TYPE.CARD, + LICENSE_ALLOCATED_STATUS.REUSABLE, + null, + null, + null, + null, + ); + await createLicense( + source, + 3, + dayAfterTomorrow, + account.id, + LICENSE_TYPE.TRIAL, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + null, + null, + null, + null, + ); + // ライセンス作成したデータを確認 + { + const license1 = await selectLicense(source, 1); + expect(license1.license?.id).toBe(1); + expect(license1.license?.expiry_date).toEqual(today); + expect(license1.license?.allocated_user_id).toBe(null); + expect(license1.license?.status).toBe( + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + ); + expect(license1.license?.account_id).toBe(account.id); + expect(license1.license?.type).toBe(LICENSE_TYPE.NORMAL); + const license2 = await selectLicense(source, 2); + expect(license2.license?.id).toBe(2); + expect(license2.license?.expiry_date).toEqual(tomorrow); + expect(license2.license?.allocated_user_id).toBe(null); + expect(license2.license?.status).toBe(LICENSE_ALLOCATED_STATUS.REUSABLE); + expect(license2.license?.account_id).toBe(account.id); + expect(license2.license?.type).toBe(LICENSE_TYPE.CARD); + const license3 = await selectLicense(source, 3); + expect(license3.license?.id).toBe(3); + expect(license3.license?.expiry_date).toEqual(dayAfterTomorrow); + expect(license3.license?.allocated_user_id).toBe(null); + expect(license3.license?.status).toBe( + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + ); + expect(license3.license?.account_id).toBe(account.id); + expect(license3.license?.type).toBe(LICENSE_TYPE.TRIAL); + } + const service = module.get(LicensesService); + const context = makeContext('trackingId', 'requestId'); + const response = await service.getAllocatableLicenses( + context, + admin.external_id, + ); + + // 有効期限が当日のライセンスは取得されない + // 有効期限が長い順に取得される + expect(response.allocatableLicenses.length).toBe(2); + expect(response.allocatableLicenses[0].licenseId).toBe(3); + expect(response.allocatableLicenses[0].expiryDate).toEqual( + dayAfterTomorrow, + ); + expect(response.allocatableLicenses[1].licenseId).toBe(2); + expect(response.allocatableLicenses[1].expiryDate).toEqual(tomorrow); + }); + + it('割り当て可能なライセンスが存在しない場合、空の配列を返却する', async () => { + if (!source) fail(); + const module = await makeTestingModule(source); + if (!module) fail(); + + const { account, admin } = await makeTestAccount(source, { + company_name: 'AAA', + tier: 5, + }); + // ライセンスを作成 + // 有効期限が当日のライセンスを3つ、有効期限が昨日のライセンスを1つ作成 + // ミリ秒以下は切り捨てる DBで扱っていないため + const today = new Date(); + today.setMilliseconds(0); + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + yesterday.setMilliseconds(0); + for (let i = 1; i <= 3; i++) { + await createLicense( + source, + i, + today, + account.id, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + null, + null, + null, + null, + ); + } + await createLicense( + source, + 4, + yesterday, + account.id, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + null, + null, + null, + null, + ); + // ライセンス作成したデータを確認 + { + const license1 = await selectLicense(source, 1); + expect(license1.license?.id).toBe(1); + expect(license1.license?.expiry_date).toEqual(today); + expect(license1.license?.allocated_user_id).toBe(null); + expect(license1.license?.status).toBe( + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + ); + expect(license1.license?.account_id).toBe(account.id); + expect(license1.license?.type).toBe(LICENSE_TYPE.NORMAL); + const license2 = await selectLicense(source, 2); + expect(license2.license?.id).toBe(2); + expect(license2.license?.expiry_date).toEqual(today); + expect(license2.license?.allocated_user_id).toBe(null); + expect(license2.license?.status).toBe( + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + ); + expect(license2.license?.account_id).toBe(account.id); + expect(license2.license?.type).toBe(LICENSE_TYPE.NORMAL); + const license3 = await selectLicense(source, 3); + expect(license3.license?.id).toBe(3); + expect(license3.license?.expiry_date).toEqual(today); + expect(license3.license?.allocated_user_id).toBe(null); + expect(license3.license?.status).toBe( + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + ); + expect(license3.license?.account_id).toBe(account.id); + expect(license3.license?.type).toBe(LICENSE_TYPE.NORMAL); + const license4 = await selectLicense(source, 4); + expect(license4.license?.id).toBe(4); + expect(license4.license?.expiry_date).toEqual(yesterday); + expect(license4.license?.allocated_user_id).toBe(null); + expect(license4.license?.status).toBe( + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + ); + expect(license4.license?.account_id).toBe(account.id); + expect(license4.license?.type).toBe(LICENSE_TYPE.NORMAL); + } + const service = module.get(LicensesService); + const context = makeContext('trackingId', 'requestId'); + const response = await service.getAllocatableLicenses( + context, + admin.external_id, + ); + // 有効期限が当日のライセンスは取得されない + // 有効期限が切れているライセンスは取得されない + expect(response.allocatableLicenses.length).toBe(0); + expect(response.allocatableLicenses).toEqual([]); + }); + + it('割り当て可能なライセンスを100件取得できる', async () => { + if (!source) fail(); + const module = await makeTestingModule(source); + if (!module) fail(); + + const { account, admin } = await makeTestAccount(source, { + company_name: 'AAA', + tier: 5, + }); + // ライセンスを作成 + // 有効期限が30日後のライセンスを100件作成 + // ミリ秒以下は切り捨てる DBで扱っていないため + const date = new Date(); + date.setDate(date.getDate() + 30); + date.setMilliseconds(0); + for (let i = 1; i <= 100; i++) { + await createLicense( + source, + i, + date, + account.id, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + null, + null, + null, + null, + ); + } + // ライセンス作成したデータを確認 + for (let i = 1; i <= 100; i++) { + const license = await selectLicense(source, i); + expect(license.license?.id).toBe(i); + expect(license.license?.expiry_date).toEqual(date); + expect(license.license?.allocated_user_id).toBe(null); + expect(license.license?.status).toBe( + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + ); + expect(license.license?.account_id).toBe(account.id); + expect(license.license?.type).toBe(LICENSE_TYPE.NORMAL); + } + const service = module.get(LicensesService); + const context = makeContext('trackingId', 'requestId'); + const response = await service.getAllocatableLicenses( + context, + admin.external_id, + ); + // 100件取得できる + expect(response.allocatableLicenses.length).toBe(100); + }); + it('既に割り当てられているライセンスは取得されない', async () => { + if (!source) fail(); + const module = await makeTestingModule(source); + if (!module) fail(); + const { account, admin } = await makeTestAccount(source, { + company_name: 'AAA', + tier: 5, + }); + // ライセンスを作成 + // ライセンスを5件作成(10日後から1日ずつ有効期限を設定) + // 有効期限が設定されていないライセンス(新規ライセンス)を1件作成 + // 既に割り当てられているライセンスを1件作成 + // ミリ秒以下は切り捨てる DBで扱っていないため + const date = new Date(); + date.setMinutes(0); + date.setSeconds(0); + date.setDate(date.getDate() + 10); + date.setMilliseconds(0); + for (let i = 1; i <= 5; i++) { + await createLicense( + source, + i, + date, + account.id, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + admin.id, + null, + null, + null, + ); + date.setDate(date.getDate() + 1); + } + // 新規ライセンス + await createLicense( + source, + 6, + null, + account.id, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + null, + null, + null, + null, + ); + // 既に割り当てられているライセンス + await createLicense( + source, + 7, + date, + account.id, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.ALLOCATED, + admin.id, + null, + null, + null, + ); + // ライセンス作成したデータを確認 + { + const date = new Date(); + date.setMinutes(0); + date.setSeconds(0); + date.setDate(date.getDate() + 10); + date.setMilliseconds(0); + for (let i = 1; i <= 5; i++) { + const license = await selectLicense(source, i); + expect(license.license?.id).toBe(i); + expect(license.license?.expiry_date).toEqual(date); + expect(license.license?.allocated_user_id).toBe(admin.id); + expect(license.license?.status).toBe( + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + ); + expect(license.license?.account_id).toBe(account.id); + expect(license.license?.type).toBe(LICENSE_TYPE.NORMAL); + date.setDate(date.getDate() + 1); + } + const newLicense = await selectLicense(source, 6); + expect(newLicense.license?.id).toBe(6); + expect(newLicense.license?.expiry_date).toBe(null); + expect(newLicense.license?.allocated_user_id).toBe(null); + expect(newLicense.license?.status).toBe( + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + ); + const allocatedLicense = await selectLicense(source, 7); + expect(allocatedLicense.license?.id).toBe(7); + expect(allocatedLicense.license?.expiry_date).toEqual(date); + expect(allocatedLicense.license?.allocated_user_id).toBe(admin.id); + expect(allocatedLicense.license?.status).toBe( + LICENSE_ALLOCATED_STATUS.ALLOCATED, + ); + expect(allocatedLicense.license?.account_id).toBe(account.id); + expect(allocatedLicense.license?.type).toBe(LICENSE_TYPE.NORMAL); + } + + const service = module.get(LicensesService); + const context = makeContext('trackingId', 'requestId'); + const response = await service.getAllocatableLicenses( + context, + admin.external_id, + ); + // 既に割り当てられているライセンスは取得されない + // 新規ライセンスは取得される + // 有効期限が長い順に取得される + expect(response.allocatableLicenses.length).toBe(6); + expect(response.allocatableLicenses[0].licenseId).toBe(6); + expect(response.allocatableLicenses[0].expiryDate).toBe(undefined); + expect(response.allocatableLicenses[1].licenseId).toBe(5); // 有効期限が最も長い + expect(response.allocatableLicenses[2].licenseId).toBe(4); + expect(response.allocatableLicenses[3].licenseId).toBe(3); + expect(response.allocatableLicenses[4].licenseId).toBe(2); + expect(response.allocatableLicenses[5].licenseId).toBe(1); + }); +}); diff --git a/dictation_server/src/repositories/licenses/licenses.repository.service.ts b/dictation_server/src/repositories/licenses/licenses.repository.service.ts index 5d20eb4..5b6e873 100644 --- a/dictation_server/src/repositories/licenses/licenses.repository.service.ts +++ b/dictation_server/src/repositories/licenses/licenses.repository.service.ts @@ -525,9 +525,14 @@ export class LicensesRepositoryService { context: Context, myAccountId: number, ): Promise { - const nowDate = new DateWithZeroTime(); const licenseRepo = this.dataSource.getRepository(License); // EntityManagerではorderBy句で、expiry_dateに対して複数条件でソートを使用するため出来ない為、createQueryBuilderを使用する。 + // プロダクト バックログ項目 4218: [FB対応]有効期限当日のライセンスは一覧に表示しない の対応 + // 有効期限が当日のライセンスは取得しない + // 明日の00:00:00を取得 + const tomorrowDate = new DateWithZeroTime( + new Date().setDate(new Date().getDate() + 1), + ); const queryBuilder = licenseRepo .createQueryBuilder('license') .where('license.account_id = :accountId', { accountId: myAccountId }) @@ -538,8 +543,8 @@ export class LicensesRepositoryService { ], }) .andWhere( - '(license.expiry_date >= :nowDate OR license.expiry_date IS NULL)', - { nowDate }, + '(license.expiry_date >= :tomorrowDate OR license.expiry_date IS NULL)', + { tomorrowDate }, ) .comment(`${context.getTrackingId()}_${new Date().toUTCString()}`) .orderBy('license.expiry_date IS NULL', 'DESC') @@ -597,6 +602,9 @@ export class LicensesRepositoryService { } // 期限切れの場合はエラー + // 有効期限が当日のライセンスは割り当て不可 + // XXX 記述は「有効期限が過去のライセンスは割り当て不可」のような意図だと思われるが、実際の処理は「有効期限が当日のライセンスは割り当て不可」になっている + // より正確な記述に修正したほうが良いが、リリース後のため、修正は保留(2024年6月7日) if (targetLicense.expiry_date) { const currentDay = new Date(); currentDay.setHours(23, 59, 59, 999);