diff --git a/dictation_server/src/features/accounts/accounts.controller.ts b/dictation_server/src/features/accounts/accounts.controller.ts index 0e27f68..6e09a8f 100644 --- a/dictation_server/src/features/accounts/accounts.controller.ts +++ b/dictation_server/src/features/accounts/accounts.controller.ts @@ -1,6 +1,7 @@ import { Body, Controller, + HttpException, HttpStatus, Post, Get, @@ -26,11 +27,20 @@ import { import { USER_ROLES, ADMIN_ROLES } from '../../constants'; import { AuthGuard } from '../../common/guards/auth/authguards'; import { RoleGuard } from '../../common/guards/role/roleguards'; +//import { CryptoService } from '../../gateways/crypto/crypto.service'; +import { retrieveAuthorizationToken } from '../../common/http/helper'; +import { makeErrorResponse } from '../../common/error/makeErrorResponse'; +import { isVerifyError, verify } from '../../common/jwt'; +import { AccessToken } from '../../common/token'; +//import { confirmPermission } from '../../common/auth/auth'; +import jwt from 'jsonwebtoken'; @ApiTags('accounts') @Controller('accounts') export class AccountsController { - constructor(private readonly accountService: AccountsService) {} + constructor( + private readonly accountService: AccountsService, //private readonly cryptoService: CryptoService, + ) {} @Post() @ApiResponse({ @@ -106,21 +116,40 @@ export class AccountsController { ): Promise { console.log(req.header('Authorization')); console.log(body); - return { - licenseSummaryInfo: { - totalLicense: 0, - allocatedLicense: 0, - reusableLicense: 0, - freeLicense: 0, - expiringWithin14daysLicense: 0, - issueRequesting: 0, - numberOfRequesting: 0, - shortage: 0, - storageSize: 0, - usedSize: 0, - isAccountLock: true, - }, - }; + // アクセストークンにより権限を確認する + /* 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 { + licenseSummaryInfo: info, + }; */ + return; } @ApiResponse({ @@ -153,9 +182,15 @@ export class AccountsController { @Get('me') async getMyAccount(@Req() req: Request): Promise { console.log(req.header('Authorization')); + + // アクセストークン取得 + const accessToken = retrieveAuthorizationToken(req); + const payload = jwt.decode(accessToken, { json: true }) as AccessToken; + //アカウントID取得処理 + const accountId = await this.accountService.getMyAccountInfo(payload); return { account: { - accountId: 1, + accountId: accountId, }, }; } diff --git a/dictation_server/src/features/accounts/accounts.service.spec.ts b/dictation_server/src/features/accounts/accounts.service.spec.ts index 628fd16..95b696f 100644 --- a/dictation_server/src/features/accounts/accounts.service.spec.ts +++ b/dictation_server/src/features/accounts/accounts.service.spec.ts @@ -1,25 +1,123 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AccountsService } from './accounts.service'; +import { HttpException, HttpStatus } from '@nestjs/common'; +import { makeErrorResponse } from '../../common/error/makeErrorResponse'; +import { + makeAccountsServiceMock, + makeDefaultAccountsRepositoryMockValue, + makeDefaultAdB2cMockValue, + makeDefaultSendGridlValue, + makeDefaultUsersRepositoryMockValue, +} from './test/accounts.service.mock'; describe('AccountsService', () => { - let service: AccountsService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [AccountsService], - }) - .useMocker(() => { - return { - createAccount: undefined, - update: undefined, - }; - }) - .compile(); - - service = module.get(AccountsService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); + it('アカウントに紐づくライセンス情報を取得する', async () => { + const user_id = 1; + const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); + const adb2cParam = makeDefaultAdB2cMockValue(); + const accountsRepositoryMockValue = + makeDefaultAccountsRepositoryMockValue(); + const sendGridMockValue = makeDefaultSendGridlValue(); + const service = await makeAccountsServiceMock( + accountsRepositoryMockValue, + usersRepositoryMockValue, + adb2cParam, + sendGridMockValue, + ); + expect(await service.getLicenseSummary(user_id)).toEqual( + expectedAccountLisenceCounts, + ); }); }); +it('ライセンス情報が取得できない場合、エラーとなる', async () => { + const user_id = 1; + const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); + usersRepositoryMockValue.findUserById = new Error('DB error'); + const adb2cParam = makeDefaultAdB2cMockValue(); + const accountsRepositoryMockValue = makeDefaultAccountsRepositoryMockValue(); + const sendGridMockValue = makeDefaultSendGridlValue(); + const service = await makeAccountsServiceMock( + accountsRepositoryMockValue, + usersRepositoryMockValue, + 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(userInfo); +}); +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( + new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ), + ); +}); + +const expectedAccountLisenceCounts = { + totalLicense: 1, + allocatedLicense: 2, + reusableLicense: 3, + freeLicense: 4, + expiringWithin14daysLicense: 5, + issueRequesting: 6, + numberOfRequesting: 7, + shortage: 8, + storageSize: 0, + usedSize: 0, + isAccountLock: false, +}; + +const userInfo = { + accepted_terms_version: '1.0', + account_id: 1234567890123456, + author_id: '6cce347f-0cf1-a15e-19ab-d00988b643f9', + auto_renew: false, + created_at: new Date('2023-06-13 00:00:00'), + created_by: 'test', + deleted_at: null, + email_verified: true, + external_id: 'ede66c43-9b9d-4222-93ed-5f11c96e08e2', + id: 1, + license_alert: false, + notification: false, + role: 'none admin', + updated_at: null, + updated_by: null, +}; diff --git a/dictation_server/src/features/accounts/accounts.service.ts b/dictation_server/src/features/accounts/accounts.service.ts index 15267ef..1ec54a6 100644 --- a/dictation_server/src/features/accounts/accounts.service.ts +++ b/dictation_server/src/features/accounts/accounts.service.ts @@ -1,7 +1,10 @@ -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { SendGridService } from '../../gateways/sendgrid/sendgrid.service'; -import { UsersRepositoryService } from '../../repositories/users/users.repository.service'; +import { + UsersRepositoryService, + UserNotFoundError, +} from '../../repositories/users/users.repository.service'; import { AccountsRepositoryService } from '../../repositories/accounts/accounts.repository.service'; import { AdB2cService, @@ -12,6 +15,9 @@ import { Account } from '../../repositories/accounts/entity/account.entity'; import { User } from '../../repositories/users/entity/user.entity'; import { 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() export class AccountsService { @@ -22,7 +28,62 @@ export class AccountsService { private readonly sendgridService: SendGridService, private readonly configService: ConfigService, ) {} + private readonly logger = new Logger(AccountsService.name); + /** + * 第五階層用のライセンス情報を取得する + * @param userId + * @returns LicenseSummary + */ + async getLicenseSummary(userId: 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, + ); + // 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); + } catch (e) { + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + return licenseSammury; + } /** * アカウント情報をDBに作成する * @param companyName @@ -126,4 +187,34 @@ export class AccountsService { externalUserId: user.external_id, }; } + + /** + * アクセストークンからアカウント情報を取得する + * @param token + * @returns accountId + */ + async getMyAccountInfo(token: AccessToken): Promise { + this.logger.log(`[IN] ${this.getMyAccountInfo.name}`); + + let userInfo: User; + try { + userInfo = await this.usersRepository.findUserByExternalId(token.userId); + } catch (e) { + switch (e.constructor) { + case UserNotFoundError: + throw new HttpException( + makeErrorResponse('E010204'), + HttpStatus.BAD_REQUEST, + ); + default: + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + this.logger.log(`[OUT] ${this.getMyAccountInfo.name}`); + return userInfo.account_id; + } } diff --git a/dictation_server/src/features/accounts/test/accounts.service.mock.ts b/dictation_server/src/features/accounts/test/accounts.service.mock.ts new file mode 100644 index 0000000..997c8bf --- /dev/null +++ b/dictation_server/src/features/accounts/test/accounts.service.mock.ts @@ -0,0 +1,221 @@ +import { ConfigModule } from '@nestjs/config'; +import { Test, TestingModule } from '@nestjs/testing'; +import { User } from '../../../repositories/users/entity/user.entity'; +import { UsersRepositoryService } from '../../../repositories/users/users.repository.service'; +import { AccountsService } from '../accounts.service'; +import { AccountsRepositoryService } from '../../../repositories/accounts/accounts.repository.service'; +import { + AdB2cService, + ConflictError, +} from '../../../gateways/adb2c/adb2c.service'; +import { SendGridService } from '../../../gateways/sendgrid/sendgrid.service'; +export type UsersRepositoryMockValue = { + findUserById: User | Error; + findUserByExternalId: User | Error; +}; +export type AdB2cMockValue = { + createUser: string | ConflictError | Error; +}; +export type SendGridMockValue = { + createMailContentFromEmailConfirm: { + subject: string; + text: string; + html: string; + }; + 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; +}; +export const makeAccountsServiceMock = async ( + accountsRepositoryMockValue: AccountsRepositoryMockValue, + usersRepositoryMockValue: UsersRepositoryMockValue, + adB2cMockValue: AdB2cMockValue, + sendGridMockValue: SendGridMockValue, +): Promise => { + const module: TestingModule = await Test.createTestingModule({ + providers: [AccountsService], + imports: [ + ConfigModule.forRoot({ + ignoreEnvFile: true, + ignoreEnvVars: true, + }), + ], + }) + .useMocker((token) => { + switch (token) { + case AccountsRepositoryService: + return makeAccountsRepositoryMock(accountsRepositoryMockValue); + case UsersRepositoryService: + return makeUsersRepositoryMock(usersRepositoryMockValue); + case AdB2cService: + return makeAdB2cServiceMock(adB2cMockValue); + case SendGridService: + return makeSendGridServiceMock(sendGridMockValue); + } + }) + .compile(); + + return module.get(AccountsService); +}; + +export const makeAccountsRepositoryMock = ( + value: AccountsRepositoryMockValue, +) => { + const { + getTotalLicense, + getAllocatedLicense, + getReusableLicense, + getFreeLicense, + getExpiringWithin14DaysLicense, + getIssueRequesting, + getNumberOfRequesting, + getShortage, + getIsAccountLocked, + } = 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) + : 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), + }; +}; +export const makeUsersRepositoryMock = (value: UsersRepositoryMockValue) => { + const { findUserById, findUserByExternalId } = 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) => { + const { createUser } = value; + + return { + createUser: + createUser instanceof Error + ? jest.fn, []>().mockRejectedValue(createUser) + : jest + .fn, []>() + .mockResolvedValue(createUser), + }; +}; +export const makeSendGridServiceMock = (value: SendGridMockValue) => { + const { createMailContentFromEmailConfirm, sendMail } = value; + return { + createMailContentFromEmailConfirm: + createMailContentFromEmailConfirm instanceof Error + ? jest + .fn, []>() + .mockRejectedValue(createMailContentFromEmailConfirm) + : jest + .fn, []>() + .mockResolvedValue(createMailContentFromEmailConfirm), + sendMail: + sendMail instanceof Error + ? jest.fn, []>().mockRejectedValue(sendMail) + : jest.fn, []>().mockResolvedValue(sendMail), + }; +}; +// 個別のテストケースに対応してそれぞれのMockを用意するのは無駄が多いのでテストケース内で個別の値を設定する +export const makeDefaultAccountsRepositoryMockValue = + (): AccountsRepositoryMockValue => { + return { + getTotalLicense: 1, + getAllocatedLicense: 2, + getReusableLicense: 3, + getFreeLicense: 4, + getExpiringWithin14DaysLicense: 5, + getIssueRequesting: 6, + getNumberOfRequesting: 7, + getShortage: 8, + getIsAccountLocked: false, + }; + }; +export const makeDefaultUsersRepositoryMockValue = + (): UsersRepositoryMockValue => { + const user = new User(); + user.id = 1; + user.external_id = 'ede66c43-9b9d-4222-93ed-5f11c96e08e2'; + user.account_id = 1234567890123456; + user.role = 'none admin'; + user.author_id = '6cce347f-0cf1-a15e-19ab-d00988b643f9'; + user.accepted_terms_version = '1.0'; + user.email_verified = true; + user.auto_renew = false; + user.license_alert = false; + user.notification = false; + user.deleted_at = null; + user.created_by = 'test'; + user.created_at = new Date('2023-06-13 00:00:00'); + user.updated_by = null; + user.updated_at = null; + + return { + findUserById: user, + findUserByExternalId: user, + }; + }; +export const makeDefaultAdB2cMockValue = (): AdB2cMockValue => { + return { + createUser: '001', + }; +}; +export const makeDefaultSendGridlValue = (): SendGridMockValue => { + return { + sendMail: undefined, + createMailContentFromEmailConfirm: { subject: '', text: '', html: '' }, + }; +}; diff --git a/dictation_server/src/repositories/accounts/accounts.repository.service.ts b/dictation_server/src/repositories/accounts/accounts.repository.service.ts index 5595fd6..7b6656d 100644 --- a/dictation_server/src/repositories/accounts/accounts.repository.service.ts +++ b/dictation_server/src/repositories/accounts/accounts.repository.service.ts @@ -1,12 +1,21 @@ import { Injectable } from '@nestjs/common'; -import { DataSource, UpdateResult } from 'typeorm'; +import { + Between, + DataSource, + IsNull, + MoreThan, + Not, + UpdateResult, +} from 'typeorm'; import { User } from '../users/entity/user.entity'; import { Account } from './entity/account.entity'; +import { License, LicenseOrder } from '../licenses/entity/license.entity'; import { SortCriteria } from '../sort_criteria/entity/sort_criteria.entity'; import { getDirection, getTaskListSortableAttribute, } from '../../common/types/sort/util'; +import { LICENSE_STATUS_ISSUE_REQUESTING } from '../../../src/constants'; export class AccountNotFoundError extends Error {} @@ -148,4 +157,186 @@ export class AccountsRepositoryService { } return account; } + /** + * アカウントIDから有効な総ライセンス数を取得する + * @param id + * @returns count + */ + 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: [ + { + account_id: id, + expiry_date: Between(currentDate, fourteenDaysLater), + }, + { account_id: id, expiry_date: Not(IsNull()) }, + ], + }; + + const count = await this.dataSource.getRepository(License).count(options); + + return count; + } + /** + * アカウントIDから未発行状態あるいは発行キャンセルされた注文数を取得する + * @param id + * @returns count + */ + async getIssueRequesting(id: number): Promise { + const count = await this.dataSource.getRepository(LicenseOrder).count({ + where: [ + { + from_account_id: id, + status: LICENSE_STATUS_ISSUE_REQUESTING, + }, + ], + }); + + 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 ad22ec9..dfaf7b0 100644 --- a/dictation_server/src/repositories/licenses/entity/license.entity.ts +++ b/dictation_server/src/repositories/licenses/entity/license.entity.ts @@ -34,3 +34,53 @@ export class LicenseOrder { @Column('timestamp', { nullable: true }) canceled_at?: Date; } + +@Entity({ name: 'license' }) +export class License { + @PrimaryGeneratedColumn() + id: number; + + @Column() + expiry_date: Date; + + @Column() + account_id: number; + + @Column() + type: string; + + @Column() + status: string; + + @Column() + allocated_user_id: number; + + @Column() + order_id: number; + + @Column() + deleted_at: Date; + + @Column() + delete_order_id: number; +} +@Entity({ name: 'license_history' }) +export class LicenseHistory { + @PrimaryGeneratedColumn() + id: number; + + @Column() + user_id: number; + + @Column() + license_id: number; + + @Column() + allocated: boolean; + + @Column() + executed_at: Date; + + @Column() + exchange_type: string; +}