Merged PR 771: 音声ファイルアップロード完了API実装(ストレージ使用量超過チェック)

## 概要
[Task3687: 音声ファイルアップロード完了API実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3687)

- 音声ファイルアップロード完了API実行時に、ストレージの使用量チェックを行い、必要ならメール送信をする実装を追加しました。

## レビューポイント
- 使用量チェックメソッドで他にいい関数名ないか?
- なるべく既存実装をいじりたくなかったので自動ルーティング前にチェック機構を配置したが不都合ないか?
- テストケースに過不足ないか
- 自動テストの実行方法や確認方法として適切か?ほかに代替案ないか?

## 動作確認状況
- ローカルでUT通ることを確認。
   - 実際のメール送信はdeveop動作確認でやります。
This commit is contained in:
Kentaro Fukunaga 2024-02-27 02:49:52 +00:00
parent ddd4d31f25
commit dd8bddc971
15 changed files with 1256 additions and 6 deletions

View File

@ -333,3 +333,9 @@ export const FILE_RETENTION_DAYS_DEFAULT = 30;
* @const {number}
*/
export const STORAGE_SIZE_PER_LICENSE = 5;
/**
* 使%
* @const {number}
*/
export const STORAGE_WARNING_THRESHOLD_PERCENT = 80;

View File

@ -10,6 +10,9 @@ import { TemplateFilesRepositoryModule } from '../../repositories/template_files
import { UserGroupsRepositoryModule } from '../../repositories/user_groups/user_groups.repository.module';
import { NotificationhubModule } from '../../gateways/notificationhub/notificationhub.module';
import { LicensesRepositoryModule } from '../../repositories/licenses/licenses.repository.module';
import { SendGridModule } from '../../gateways/sendgrid/sendgrid.module';
import { AdB2cModule } from '../../gateways/adb2c/adb2c.module';
import { AccountsRepositoryModule } from '../../repositories/accounts/accounts.repository.module';
@Module({
imports: [
@ -22,6 +25,9 @@ import { LicensesRepositoryModule } from '../../repositories/licenses/licenses.r
UserGroupsRepositoryModule,
NotificationhubModule,
LicensesRepositoryModule,
SendGridModule,
AdB2cModule,
AccountsRepositoryModule,
],
providers: [FilesService],
controllers: [FilesController],

View File

@ -18,7 +18,10 @@ import {
makeTestUser,
} from '../../common/test/utility';
import { makeTestingModule } from '../../common/test/modules';
import { overrideBlobstorageService } from '../../common/test/overrides';
import {
overrideAdB2cService,
overrideBlobstorageService,
} from '../../common/test/overrides';
import {
createTemplateFile,
getTemplateFiles,
@ -768,6 +771,353 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
// 自動ルーティングが行われていないことを確認
expect(resultCheckoutPermission.length).toEqual(0);
});
it('第五階層アカウントのストレージ使用量が閾値と同値の場合、メール送信が行われない', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// アカウントを作成する
const { id: accountId } = (
await makeTestAccount(source, {
tier: 5,
company_name: 'company1',
})
).account;
// 音声ファイルの録音者のユーザー
const { external_id: authorExternalId, author_id: authorAuthorId } =
await makeTestUser(source, {
account_id: accountId,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
});
// ライセンスを作成する。ライセンスが2つのため、5GB * 2 = 10GBの上限値となる。
// 閾値は、10GB * 0.8 = 8GBとなる。
const reusableLicense = 2;
for (let i = 0; i < reusableLicense; i++) {
await createLicense(
source,
i + 1,
new Date(2037, 1, 1, 23, 59, 59),
accountId,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.REUSABLE,
null,
null,
null,
null,
);
}
const fileSize = 2 * 1000 * 1000 * 1000; // 2GB
// 3つの音声ファイルを事前作成uploadFinishedで、合計8GBのストレージ使用量状態を作成
for (let i = 0; i < 3; i++) {
await createTask(
source,
accountId,
'url',
'test.zip',
'InProgress',
undefined,
authorAuthorId ?? '',
undefined,
fileSize,
(i + 1).toString().padStart(8, '0'),
);
}
const service = module.get<FilesService>(FilesService);
const spy = jest
.spyOn(service['sendGridService'], 'sendMail')
.mockImplementation();
const context = makeContext(`uuidv4`, 'xxx-xxx-xxx-xxx', 'requestId');
await service.uploadFinished(
context,
authorExternalId, // API実行者のユーザーIDを設定
'http://blob/url/file.zip',
authorAuthorId ?? '', // 音声ファイルの情報には、録音者のAuthorIDが入る
'file.zip',
'11:22:33',
'2023-05-26T11:22:33.444',
'2023-05-26T11:22:33.444',
'2023-05-26T11:22:33.444',
fileSize,
'01',
'DS2',
'comment',
'worktypeId',
optionItemList,
false,
);
expect(spy).not.toHaveBeenCalled();
});
it('第五階層アカウントのストレージ使用量が閾値+1byteの場合、U-118メール送信が行われる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// アカウントを作成する
const { id: accountId } = (
await makeTestAccount(source, {
tier: 5,
company_name: 'company1',
})
).account;
// 音声ファイルの録音者のユーザー
const { external_id: authorExternalId, author_id: authorAuthorId } =
await makeTestUser(source, {
account_id: accountId,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
});
// ライセンスを作成する。ライセンスが2つのため、5GB * 2 = 10GBの上限値となる。
// 閾値は、10GB * 0.8 = 8GBとなる。
const reusableLicense = 2;
for (let i = 0; i < reusableLicense; i++) {
await createLicense(
source,
i + 1,
new Date(2037, 1, 1, 23, 59, 59),
accountId,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.REUSABLE,
null,
null,
null,
null,
);
}
const fileSize = 2 * 1000 * 1000 * 1000; // 2GB
// 3つの音声ファイルを事前作成uploadFinishedで、合計8GB+1byteのストレージ使用量状態を作成
for (let i = 0; i < 3; i++) {
await createTask(
source,
accountId,
'url',
'test.zip',
'InProgress',
undefined,
authorAuthorId ?? '',
undefined,
fileSize,
(i + 1).toString().padStart(8, '0'),
);
}
const service = module.get<FilesService>(FilesService);
// メール送信関数が呼ばれたかどうかで判定を行う。実際のメール送信は行わない。
const spy = jest
.spyOn(service['sendGridService'], 'sendMailWithU118')
.mockImplementation();
overrideAdB2cService(service, {
getUsers: async () => [],
});
const context = makeContext(`uuidv4`, 'xxx-xxx-xxx-xxx', 'requestId');
await service.uploadFinished(
context,
authorExternalId, // API実行者のユーザーIDを設定
'http://blob/url/file.zip',
authorAuthorId ?? '', // 音声ファイルの情報には、録音者のAuthorIDが入る
'file.zip',
'11:22:33',
'2023-05-26T11:22:33.444',
'2023-05-26T11:22:33.444',
'2023-05-26T11:22:33.444',
fileSize + 1,
'01',
'DS2',
'comment',
'worktypeId',
optionItemList,
false,
);
expect(spy).toHaveBeenCalledTimes(1);
});
it('第五階層アカウントのストレージ使用量が上限と同値の場合、U-118メール送信が行われる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// アカウントを作成する
const { id: accountId } = (
await makeTestAccount(source, {
tier: 5,
company_name: 'company1',
})
).account;
// 音声ファイルの録音者のユーザー
const { external_id: authorExternalId, author_id: authorAuthorId } =
await makeTestUser(source, {
account_id: accountId,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
});
// ライセンスを作成する。ライセンスが2つのため、5GB * 2 = 10GBの上限値となる。
const reusableLicense = 2;
for (let i = 0; i < reusableLicense; i++) {
await createLicense(
source,
i + 1,
new Date(2037, 1, 1, 23, 59, 59),
accountId,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.REUSABLE,
null,
null,
null,
null,
);
}
const fileSize = 2 * 1000 * 1000 * 1000; // 2GB
// 4つの音声ファイルを事前作成uploadFinishedで、合計10GBのストレージ使用量状態を作成
for (let i = 0; i < 4; i++) {
await createTask(
source,
accountId,
'url',
'test.zip',
'InProgress',
undefined,
authorAuthorId ?? '',
undefined,
fileSize,
(i + 1).toString().padStart(8, '0'),
);
}
const service = module.get<FilesService>(FilesService);
// メール送信関数が呼ばれたかどうかで判定を行う。実際のメール送信は行わない。
const spy = jest
.spyOn(service['sendGridService'], 'sendMailWithU118')
.mockImplementation();
overrideAdB2cService(service, {
getUsers: async () => [],
});
const context = makeContext(`uuidv4`, 'xxx-xxx-xxx-xxx', 'requestId');
await service.uploadFinished(
context,
authorExternalId, // API実行者のユーザーIDを設定
'http://blob/url/file.zip',
authorAuthorId ?? '', // 音声ファイルの情報には、録音者のAuthorIDが入る
'file.zip',
'11:22:33',
'2023-05-26T11:22:33.444',
'2023-05-26T11:22:33.444',
'2023-05-26T11:22:33.444',
fileSize,
'01',
'DS2',
'comment',
'worktypeId',
optionItemList,
false,
);
expect(spy).toHaveBeenCalledTimes(1);
});
it('第五階層アカウントのストレージ使用量が上限+1byteと同値の場合、U-119メール送信が行われる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// アカウントを作成する
const { id: accountId } = (
await makeTestAccount(source, {
tier: 5,
company_name: 'company1',
})
).account;
// 第一階層アカウントを作成する
await makeTestAccount(source, {
tier: 1,
});
// 音声ファイルの録音者のユーザー
const { external_id: authorExternalId, author_id: authorAuthorId } =
await makeTestUser(source, {
account_id: accountId,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
});
// ライセンスを作成する。ライセンスが2つのため、5GB * 2 = 10GBの上限値となる。
const reusableLicense = 2;
for (let i = 0; i < reusableLicense; i++) {
await createLicense(
source,
i + 1,
new Date(2037, 1, 1, 23, 59, 59),
accountId,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.REUSABLE,
null,
null,
null,
null,
);
}
const fileSize = 2 * 1000 * 1000 * 1000; // 2GB
// 4つの音声ファイルを事前作成uploadFinishedで、合計10GB+1byteのストレージ使用量状態を作成
for (let i = 0; i < 4; i++) {
await createTask(
source,
accountId,
'url',
'test.zip',
'InProgress',
undefined,
authorAuthorId ?? '',
undefined,
fileSize,
(i + 1).toString().padStart(8, '0'),
);
}
const service = module.get<FilesService>(FilesService);
// メール送信関数が呼ばれたかどうかで判定を行う。実際のメール送信は行わない。
const spy = jest
.spyOn(service['sendGridService'], 'sendMailWithU119')
.mockImplementation();
overrideAdB2cService(service, {
getUsers: async () => [],
});
const context = makeContext(`uuidv4`, 'xxx-xxx-xxx-xxx', 'requestId');
await service.uploadFinished(
context,
authorExternalId, // API実行者のユーザーIDを設定
'http://blob/url/file.zip',
authorAuthorId ?? '', // 音声ファイルの情報には、録音者のAuthorIDが入る
'file.zip',
'11:22:33',
'2023-05-26T11:22:33.444',
'2023-05-26T11:22:33.444',
'2023-05-26T11:22:33.444',
fileSize + 1,
'01',
'DS2',
'comment',
'worktypeId',
optionItemList,
false,
);
expect(spy).toHaveBeenCalledTimes(1);
});
it('日付フォーマットが不正な場合、エラーを返却する', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);

View File

@ -6,6 +6,7 @@ import { BlobstorageService } from '../../gateways/blobstorage/blobstorage.servi
import { AudioOptionItem, AudioUploadFinishedResponse } from './types/types';
import {
OPTION_ITEM_NUM,
STORAGE_WARNING_THRESHOLD_PERCENT,
TASK_STATUS,
TIERS,
USER_LICENSE_STATUS,
@ -37,11 +38,18 @@ import {
LicenseNotAllocatedError,
} from '../../repositories/licenses/errors/types';
import { LicensesRepositoryService } from '../../repositories/licenses/licenses.repository.service';
import { DateWithZeroTime } from '../licenses/types/types';
import { AccountsRepositoryService } from '../../repositories/accounts/accounts.repository.service';
import { getUserNameAndMailAddress } from '../../gateways/adb2c/utils/utils';
import { AdB2cService } from '../../gateways/adb2c/adb2c.service';
import { SendGridService } from '../../gateways/sendgrid/sendgrid.service';
@Injectable()
export class FilesService {
private readonly logger = new Logger(FilesService.name);
constructor(
private readonly accountsRepository: AccountsRepositoryService,
private readonly adB2cService: AdB2cService,
private readonly usersRepository: UsersRepositoryService,
private readonly tasksRepository: TasksRepositoryService,
private readonly tasksRepositoryService: TasksRepositoryService,
@ -50,6 +58,7 @@ export class FilesService {
private readonly userGroupsRepositoryService: UserGroupsRepositoryService,
private readonly notificationhubService: NotificationhubService,
private readonly licensesRepository: LicensesRepositoryService,
private readonly sendGridService: SendGridService,
) {}
/**
@ -211,11 +220,20 @@ export class FilesService {
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.uploadFinished.name}`,
);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
// 第五階層アカウントはストレージ使用量超過チェックをする。順番は自動ルーティングの前後どちらでも構わない。
if (user.account?.tier === TIERS.TIER5) {
await this.checkAndAlertStorageUsage(context, user.account_id);
}
try {
// ルーティング設定に従い、チェックアウト権限を付与する
const { typistGroupIds, typistIds } =
@ -271,6 +289,135 @@ export class FilesService {
}
}
/**
* 使
* @param context
* @param accountId ID
* @returns
*/
private async checkAndAlertStorageUsage(
context: Context,
accountId: number,
): Promise<void> {
try {
const currentDate = new DateWithZeroTime();
const { size, used } = await this.licensesRepository.getStorageInfo(
context,
accountId,
currentDate,
);
const storageShresholdSize =
(size * STORAGE_WARNING_THRESHOLD_PERCENT) / 100;
if (used > size) {
this.logger.log(
`[${context.getTrackingId()}] ${
this.checkAndAlertStorageUsage.name
} | Storage usage is over limit. accountId=${accountId}, size=${size}, used=${used}`,
);
const tier1AccountId = (
await this.accountsRepository.findTier1Account(context)
).id;
const tire1AdminMails = (
await this.getAccountInformation(context, tier1AccountId)
).adminEmails;
const dealer = await this.accountsRepository.findParentAccount(
context,
accountId,
);
const dealerName: string | null = dealer?.company_name ?? null;
const { companyName, adminEmails } = await this.getAccountInformation(
context,
accountId,
);
await this.sendGridService.sendMailWithU119(
context,
adminEmails,
companyName,
dealerName,
tire1AdminMails,
);
return;
}
if (used > storageShresholdSize) {
this.logger.log(
`[${context.getTrackingId()}] ${
this.checkAndAlertStorageUsage.name
} | Storage usage is over shresholdSize. accountId=${accountId}, size=${size}, storageShresholdSize=${storageShresholdSize}`,
);
const dealer = await this.accountsRepository.findParentAccount(
context,
accountId,
);
const dealerName: string | null = dealer?.company_name ?? null;
const { companyName, adminEmails } = await this.getAccountInformation(
context,
accountId,
);
await this.sendGridService.sendMailWithU118(
context,
adminEmails,
companyName,
dealerName,
);
return;
}
} catch (error) {
// uploadする度にストレージ使用量チェックするため、一連の処理に失敗しても例外は握りつぶす
this.logger.error(`[${context.getTrackingId()}] error=${error}`);
}
}
/**
* IDを指定して
* @param context
* @param accountId ID
* @returns /
*/
private async getAccountInformation(
context: Context,
accountId: number,
): Promise<{
companyName: string;
adminEmails: string[];
}> {
// アカウントIDから企業名を取得する
const { company_name } = await this.accountsRepository.findAccountById(
context,
accountId,
);
// 管理者一覧を取得
const admins = await this.usersRepository.findAdminUsers(
context,
accountId,
);
const adminExternalIDs = admins.map((x) => x.external_id);
// ADB2Cから管理者IDを元にメールアドレスを取得する
const usersInfo = await this.adB2cService.getUsers(
context,
adminExternalIDs,
);
// 生のAzure AD B2Cのユーザー情報からメールアドレスを抽出する
const adminEmails = usersInfo.map((x) => {
const { emailAddress } = getUserNameAndMailAddress(x);
if (emailAddress == null) {
throw new Error('admin email-address is not found');
}
return emailAddress;
});
return {
companyName: company_name,
adminEmails: adminEmails,
};
}
/**
* Publishs upload sas
* @param companyName

View File

@ -54,6 +54,8 @@ export const createTask = async (
typist_user_id?: number | undefined,
author_id?: string | undefined,
owner_user_id?: number | undefined,
fileSize?: number | undefined,
jobNumber?: string | undefined,
): Promise<{ audioFileId: number }> => {
const { identifiers: audioFileIdentifiers } = await datasource
.getRepository(AudioFile)
@ -68,7 +70,7 @@ export const createTask = async (
duration: '100000',
finished_at: new Date(),
uploaded_at: new Date(),
file_size: 10000,
file_size: fileSize ?? 10000,
priority: '00',
audio_format: 'audio_format',
is_encrypted: true,
@ -88,7 +90,7 @@ export const createTask = async (
const templateFile = templateFileIdentifiers.pop() as TemplateFile;
await datasource.getRepository(Task).insert({
job_number: '00000001',
job_number: jobNumber ?? '00000001',
account_id: account_id,
is_job_number_enabled: true,
audio_file_id: audioFile.id,

View File

@ -63,6 +63,14 @@ export class SendGridService {
private readonly templateU116Text: string;
private readonly templateU117Html: string;
private readonly templateU117Text: string;
private readonly templateU118Html: string;
private readonly templateU118Text: string;
private readonly templateU118NoParentHtml: string;
private readonly templateU118NoParentText: string;
private readonly templateU119Html: string;
private readonly templateU119Text: string;
private readonly templateU119NoParentHtml: string;
private readonly templateU119NoParentText: string;
constructor(private readonly configService: ConfigService) {
this.appDomain = this.configService.getOrThrow<string>('APP_DOMAIN');
@ -209,6 +217,44 @@ export class SendGridService {
path.resolve(__dirname, `../../templates/template_U_117.txt`),
'utf-8',
);
this.templateU118Html = readFileSync(
path.resolve(__dirname, `../../templates/template_U_118.html`),
'utf-8',
);
this.templateU118Text = readFileSync(
path.resolve(__dirname, `../../templates/template_U_118.txt`),
'utf-8',
);
this.templateU118NoParentHtml = readFileSync(
path.resolve(
__dirname,
`../../templates/template_U_118_no_parent.html`,
),
'utf-8',
);
this.templateU118NoParentText = readFileSync(
path.resolve(__dirname, `../../templates/template_U_118_no_parent.txt`),
'utf-8',
);
this.templateU119Html = readFileSync(
path.resolve(__dirname, `../../templates/template_U_119.html`),
'utf-8',
);
this.templateU119Text = readFileSync(
path.resolve(__dirname, `../../templates/template_U_119.txt`),
'utf-8',
);
this.templateU119NoParentHtml = readFileSync(
path.resolve(
__dirname,
`../../templates/template_U_119_no_parent.html`,
),
'utf-8',
);
this.templateU119NoParentText = readFileSync(
path.resolve(__dirname, `../../templates/template_U_119_no_parent.txt`),
'utf-8',
);
}
}
@ -974,6 +1020,124 @@ export class SendGridService {
}
}
/**
* U-118使
* @param context
* @param customerAdminMails (primary/secondary)
* @param customerAccountName
* @param dealerAccountName
* @returns mail with u118
*/
async sendMailWithU118(
context: Context,
customerAdminMails: string[],
customerAccountName: string,
dealerAccountName: string | null,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.sendMailWithU118.name}`,
);
try {
const subject = 'Storage Usage Worning Notification [U-118]';
let html: string;
let text: string;
if (!dealerAccountName) {
html = this.templateU118NoParentHtml.replaceAll(
CUSTOMER_NAME,
customerAccountName,
);
text = this.templateU118NoParentText.replaceAll(
CUSTOMER_NAME,
customerAccountName,
);
} else {
html = this.templateU118Html
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(DEALER_NAME, dealerAccountName);
text = this.templateU118Text
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(DEALER_NAME, dealerAccountName);
}
// メールを送信する
await this.sendMail(
context,
customerAdminMails,
[],
this.mailFrom,
subject,
text,
html,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.sendMailWithU118.name}`,
);
}
}
/**
* U-119使
* @param context
* @param customerAdminMails (primary/secondary)
* @param customerAccountName
* @param dealerAccountName
* @param tire1AdminMails (primary/secondary)
* @returns mail with u119
*/
async sendMailWithU119(
context: Context,
customerAdminMails: string[],
customerAccountName: string,
dealerAccountName: string | null,
tire1AdminMails: string[],
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.sendMailWithU119.name}`,
);
try {
const subject = 'Storage Usage Exceeded Notification [U-119]';
let html: string;
let text: string;
if (!dealerAccountName) {
html = this.templateU119NoParentHtml.replaceAll(
CUSTOMER_NAME,
customerAccountName,
);
text = this.templateU119NoParentText.replaceAll(
CUSTOMER_NAME,
customerAccountName,
);
} else {
html = this.templateU119Html
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(DEALER_NAME, dealerAccountName);
text = this.templateU119Text
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(DEALER_NAME, dealerAccountName);
}
// メールを送信する
await this.sendMail(
context,
customerAdminMails,
tire1AdminMails,
this.mailFrom,
subject,
text,
html,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.sendMailWithU119.name}`,
);
}
}
/**
*
* @param context

View File

@ -279,6 +279,63 @@ export class AccountsRepositoryService {
return account;
}
/**
* OMDSTokyoのアカウント情報を取得する
* @param id
* @returns account
*/
async findTier1Account(context: Context): Promise<Account> {
const account = await this.dataSource.getRepository(Account).findOne({
where: {
tier: TIERS.TIER1,
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
});
if (!account) {
throw new AccountNotFoundError(`Account is Not Found.`);
}
return account;
}
/**
* nullを返します
* @param context
* @param accountId
* @returns parent account
*/
async findParentAccount(
context: Context,
accountId: number,
): Promise<Account | null> {
return await this.dataSource.transaction(async (entityManager) => {
const accountsRepo = entityManager.getRepository(Account);
const myAccount = await accountsRepo.findOne({
where: {
id: accountId,
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
});
if (!myAccount) {
throw new AccountNotFoundError(`Target Account is Not Found.`);
}
if (!myAccount.parent_account_id) {
// 親アカウントが存在しない場合は明示的にnullを返す
return null;
}
const parentAccount = await accountsRepo.findOne({
where: {
id: myAccount.parent_account_id,
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
});
return parentAccount;
});
}
/**
*
*
@ -942,11 +999,11 @@ export class AccountsRepositoryService {
/**
*
* @param accountId
* @param tier
* @param accountId ID
* @param tier
* @returns account: 一階層上のアカウント
*/
async getOneUpperTierAccount(
private async getOneUpperTierAccount(
context: Context,
accountId: number,
tier: number,

View File

@ -0,0 +1,94 @@
<html>
<head>
<title>Storage Usage Worning Notification [U-118]</title>
</head>
<body>
<div>
<h3>&lt;English&gt;</h3>
<p>Dear $CUSTOMER_NAME$,</p>
<p>
The storage usage for your account has reached 80% of its usage limit.
Functions related to the Dictation Workfrow will be restricted until the
storage usage becomes lower than the limit.
</p>
<p>
Please remove Dictations files once the transcription is completed or
add capacity by assigning a license to a new user. 5GB of storage will
be provided to the account for each active user.
</p>
<p>
For detailed information, please sign in to ODMS Cloud and check the
"Subscription" tab.
</p>
<p>
If you need support regarding ODMS Cloud, please contact $DEALER_NAME$.
</p>
<p>
If you have received this e-mail in error, please delete this e-mail
from your system.<br />
This is an automatically generated e-mail and this mailbox is not
monitored. Please do not reply.
</p>
</div>
<div>
<h3>&lt;Deutsch&gt;</h3>
<p>Sehr geehrte(r) $CUSTOMER_NAME$,</p>
<p>
Die Speichernutzung Ihres Kontos hat 80 % des Nutzungslimits erreicht.
Funktionen im Zusammenhang mit dem Dictation Workfrow werden
eingeschränkt, bis die Speichernutzung unter den Grenzwert sinkt.
</p>
<p>
Bitte entfernen Sie Diktatdateien, sobald die Transkription
abgeschlossen ist, oder erhöhen Sie die Kapazität, indem Sie einem neuen
Benutzer eine Lizenz zuweisen. Für jeden aktiven Benutzer werden dem
Konto 5 GB Speicherplatz zur Verfügung gestellt.
</p>
<p>
Für detaillierte Informationen melden Sie sich bitte bei ODMS Cloud an
und überprüfen Sie die Registerkarte „Abonnement“.
</p>
<p>
Wenn Sie Unterstützung bezüglich ODMS Cloud benötigen, wenden Sie sich
bitte an $DEALER_NAME$.
</p>
<p>
Wenn Sie diese E-Mail fälschlicherweise erhalten haben, löschen Sie
diese E-Mail bitte aus Ihrem System.<br />
Dies ist eine automatisch generierte E-Mail und dieses Postfach wird
nicht überwacht. Bitte nicht antworten.
</p>
</div>
<div>
<h3>&lt;Français&gt;</h3>
<p>Chère/Cher $CUSTOMER_NAME$,</p>
<p>
L'utilisation du stockage pour votre compte a atteint 80 % de sa limite
d'utilisation. Les fonctions liées au Workfrow de dictée seront
restreintes jusqu'à ce que l'utilisation du stockage devienne inférieure
à la limite.
</p>
<p>
Veuillez supprimer les fichiers de dictées une fois la transcription
terminée ou ajouter de la capacité en attribuant une licence à un nouvel
utilisateur. 5 Go de stockage seront fournis au compte pour chaque
utilisateur actif.
</p>
<p>
Pour des informations détaillées, veuillez vous connecter à ODMS Cloud
et consulter l'onglet « Abonnement ».
</p>
<p>
Si vous avez besoin d'assistance concernant ODMS Cloud, veuillez
contacter $DEALER_NAME$.
</p>
<p>
Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail
de votre système.<br />
Il s'agit d'un e-mail généré automatiquement et cette boîte aux lettres
n'est pas surveillée. Merci de ne pas répondre.
</p>
</div>
</body>
</html>

View File

@ -0,0 +1,44 @@
<English>
Dear $CUSTOMER_NAME$,
The storage usage for your account has reached 80% of its usage limit. Functions related to the Dictation Workfrow will be restricted until the storage usage becomes lower than the limit.
Please remove Dictations files once the transcription is completed or add capacity by assigning a license to a new user. 5GB of storage will be provided to the account for each active user.
For detailed information, please sign in to ODMS Cloud and check the "Subscription" tab.
If you need support regarding ODMS Cloud, please contact $DEALER_NAME$.
If you have received this e-mail in error, please delete this e-mail from your system.
This is an automatically generated e-mail and this mailbox is not monitored. Please do not reply.
<Deutsch>
Sehr geehrte(r) $CUSTOMER_NAME$,
Die Speichernutzung Ihres Kontos hat 80 % des Nutzungslimits erreicht. Funktionen im Zusammenhang mit dem Dictation Workfrow werden eingeschränkt, bis die Speichernutzung unter den Grenzwert sinkt.
Bitte entfernen Sie Diktatdateien, sobald die Transkription abgeschlossen ist, oder erhöhen Sie die Kapazität, indem Sie einem neuen Benutzer eine Lizenz zuweisen. Für jeden aktiven Benutzer werden dem Konto 5 GB Speicherplatz zur Verfügung gestellt.
Für detaillierte Informationen melden Sie sich bitte bei ODMS Cloud an und überprüfen Sie die Registerkarte Pour des informations détaillées, veuillez vous connecter à ODMS Cloud et consulter l'onglet « Abonnement ».
Wenn Sie Unterstützung bezüglich ODMS Cloud benötigen, wenden Sie sich bitte an $DEALER_NAME$.
Wenn Sie diese E-Mail fälschlicherweise erhalten haben, löschen Sie diese E-Mail bitte aus Ihrem System.
Dies ist eine automatisch generierte E-Mail und dieses Postfach wird nicht überwacht. Bitte nicht antworten.
<Français>
Chère/Cher $CUSTOMER_NAME$,
L'utilisation du stockage pour votre compte a atteint 80 % de sa limite d'utilisation. Les fonctions liées au Workfrow de dictée seront restreintes jusqu'à ce que l'utilisation du stockage devienne inférieure à la limite.
Veuillez supprimer les fichiers de dictées une fois la transcription terminée ou ajouter de la capacité en attribuant une licence à un nouvel utilisateur. 5 Go de stockage seront fournis au compte pour chaque utilisateur actif.
Pour des informations détaillées, veuillez vous connecter à ODMS Cloud et consulter l'onglet « Abonnement ».
Si vous avez besoin d'assistance concernant ODMS Cloud, veuillez contacter $DEALER_NAME$.
Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail de votre système.
Il s'agit d'un e-mail généré automatiquement et cette boîte aux lettres n'est pas surveillée. Merci de ne pas répondre.

View File

@ -0,0 +1,83 @@
<html>
<head>
<title>Storage Usage Worning Notification [U-118]</title>
</head>
<body>
<div>
<h3>&lt;English&gt;</h3>
<p>Dear $CUSTOMER_NAME$,</p>
<p>
The storage usage for your account has reached 80% of its usage limit.
Functions related to the Dictation Workfrow will be restricted until the
storage usage becomes lower than the limit.
</p>
<p>
Please remove Dictations files once the transcription is completed or
add capacity by assigning a license to a new user. 5GB of storage will
be provided to the account for each active user.
</p>
<p>
For detailed information, please sign in to ODMS Cloud and check the
"Subscription" tab.
</p>
<p>
If you have received this e-mail in error, please delete this e-mail
from your system.<br />
This is an automatically generated e-mail and this mailbox is not
monitored. Please do not reply.
</p>
</div>
<div>
<h3>&lt;Deutsch&gt;</h3>
<p>Sehr geehrte(r) $CUSTOMER_NAME$,</p>
<p>
Die Speichernutzung Ihres Kontos hat 80 % des Nutzungslimits erreicht.
Funktionen im Zusammenhang mit dem Dictation Workfrow werden
eingeschränkt, bis die Speichernutzung unter den Grenzwert sinkt.
</p>
<p>
Bitte entfernen Sie Diktatdateien, sobald die Transkription
abgeschlossen ist, oder erhöhen Sie die Kapazität, indem Sie einem neuen
Benutzer eine Lizenz zuweisen. Für jeden aktiven Benutzer werden dem
Konto 5 GB Speicherplatz zur Verfügung gestellt.
</p>
<p>
Für detaillierte Informationen melden Sie sich bitte bei ODMS Cloud an
und überprüfen Sie die Registerkarte „Abonnement“.
</p>
<p>
Wenn Sie diese E-Mail fälschlicherweise erhalten haben, löschen Sie
diese E-Mail bitte aus Ihrem System.<br />
Dies ist eine automatisch generierte E-Mail und dieses Postfach wird
nicht überwacht. Bitte nicht antworten.
</p>
</div>
<div>
<h3>&lt;Français&gt;</h3>
<p>Chère/Cher $CUSTOMER_NAME$,</p>
<p>
L'utilisation du stockage pour votre compte a atteint 80 % de sa limite
d'utilisation. Les fonctions liées au Workfrow de dictée seront
restreintes jusqu'à ce que l'utilisation du stockage devienne inférieure
à la limite.
</p>
<p>
Veuillez supprimer les fichiers de dictées une fois la transcription
terminée ou ajouter de la capacité en attribuant une licence à un nouvel
utilisateur. 5 Go de stockage seront fournis au compte pour chaque
utilisateur actif.
</p>
<p>
Pour des informations détaillées, veuillez vous connecter à ODMS Cloud
et consulter l'onglet « Abonnement ».
</p>
<p>
Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail
de votre système.<br />
Il s'agit d'un e-mail généré automatiquement et cette boîte aux lettres
n'est pas surveillée. Merci de ne pas répondre.
</p>
</div>
</body>
</html>

View File

@ -0,0 +1,38 @@
<English>
Dear $CUSTOMER_NAME$,
The storage usage for your account has reached 80% of its usage limit. Functions related to the Dictation Workfrow will be restricted until the storage usage becomes lower than the limit.
Please remove Dictations files once the transcription is completed or add capacity by assigning a license to a new user. 5GB of storage will be provided to the account for each active user.
For detailed information, please sign in to ODMS Cloud and check the "Subscription" tab.
If you have received this e-mail in error, please delete this e-mail from your system.
This is an automatically generated e-mail and this mailbox is not monitored. Please do not reply.
<Deutsch>
Sehr geehrte(r) $CUSTOMER_NAME$,
Die Speichernutzung Ihres Kontos hat 80 % des Nutzungslimits erreicht. Funktionen im Zusammenhang mit dem Dictation Workfrow werden eingeschränkt, bis die Speichernutzung unter den Grenzwert sinkt.
Bitte entfernen Sie Diktatdateien, sobald die Transkription abgeschlossen ist, oder erhöhen Sie die Kapazität, indem Sie einem neuen Benutzer eine Lizenz zuweisen. Für jeden aktiven Benutzer werden dem Konto 5 GB Speicherplatz zur Verfügung gestellt.
Für detaillierte Informationen melden Sie sich bitte bei ODMS Cloud an und überprüfen Sie die Registerkarte Pour des informations détaillées, veuillez vous connecter à ODMS Cloud et consulter l'onglet « Abonnement ».
Wenn Sie diese E-Mail fälschlicherweise erhalten haben, löschen Sie diese E-Mail bitte aus Ihrem System.
Dies ist eine automatisch generierte E-Mail und dieses Postfach wird nicht überwacht. Bitte nicht antworten.
<Français>
Chère/Cher $CUSTOMER_NAME$,
L'utilisation du stockage pour votre compte a atteint 80 % de sa limite d'utilisation. Les fonctions liées au Workfrow de dictée seront restreintes jusqu'à ce que l'utilisation du stockage devienne inférieure à la limite.
Veuillez supprimer les fichiers de dictées une fois la transcription terminée ou ajouter de la capacité en attribuant une licence à un nouvel utilisateur. 5 Go de stockage seront fournis au compte pour chaque utilisateur actif.
Pour des informations détaillées, veuillez vous connecter à ODMS Cloud et consulter l'onglet « Abonnement ».
Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail de votre système.
Il s'agit d'un e-mail généré automatiquement et cette boîte aux lettres n'est pas surveillée. Merci de ne pas répondre.

View File

@ -0,0 +1,94 @@
<html>
<head>
<title>Storage Usage Exceeded Notification [U-119]</title>
</head>
<body>
<div>
<h3>&lt;English&gt;</h3>
<p>Dear $CUSTOMER_NAME$,</p>
<p>
The storage usage for your account has exceeded the usage limit.
Functions related to the Dictation Workfrow will be restricted until the
storage usage becomes lower than the limit.
</p>
<p>
Please remove Dictations files once the transcription is completed or
add capacity by assigning a license to a new user. 5GB of storage will
be provided to the account for each active user.
</p>
<p>
For detailed information, please sign in to ODMS Cloud and check the
"Subscription" tab.
</p>
<p>
If you need support regarding ODMS Cloud, please contact $DEALER_NAME$.
</p>
<p>
If you have received this e-mail in error, please delete this e-mail
from your system.<br />
This is an automatically generated e-mail and this mailbox is not
monitored. Please do not reply.
</p>
</div>
<div>
<h3>&lt;Deutsch&gt;</h3>
<p>Sehr geehrte(r) $CUSTOMER_NAME$,</p>
<p>
Die Speichernutzung Ihres Kontos hat das Nutzungslimit überschritten.
Funktionen im Zusammenhang mit dem Dictation Workfrow werden
eingeschränkt, bis die Speichernutzung unter den Grenzwert sinkt.
</p>
<p>
Bitte entfernen Sie Diktatdateien, sobald die Transkription
abgeschlossen ist, oder erhöhen Sie die Kapazität, indem Sie einem neuen
Benutzer eine Lizenz zuweisen. Für jeden aktiven Benutzer werden dem
Konto 5 GB Speicherplatz zur Verfügung gestellt.
</p>
<p>
Für detaillierte Informationen melden Sie sich bitte bei ODMS Cloud an
und überprüfen Sie die Registerkarte „Abonnement“.
</p>
<p>
Wenn Sie Unterstützung bezüglich ODMS Cloud benötigen, wenden Sie sich
bitte an $DEALER_NAME$.
</p>
<p>
Wenn Sie diese E-Mail fälschlicherweise erhalten haben, löschen Sie
diese E-Mail bitte aus Ihrem System.<br />
Dies ist eine automatisch generierte E-Mail und dieses Postfach wird
nicht überwacht. Bitte nicht antworten.
</p>
</div>
<div>
<h3>&lt;Français&gt;</h3>
<p>Chère/Cher $CUSTOMER_NAME$,</p>
<p>
L'utilisation du stockage pour votre compte a dépassé la limite
d'utilisation. Les fonctions liées au Workfrow de dictée seront
restreintes jusqu'à ce que l'utilisation du stockage devienne inférieure
à la limite.
</p>
<p>
Veuillez supprimer les fichiers de dictées une fois la transcription
terminée ou ajouter de la capacité en attribuant une licence à un nouvel
utilisateur. 5 Go de stockage seront fournis au compte pour chaque
utilisateur actif.
</p>
<p>
Pour des informations détaillées, veuillez vous connecter à ODMS Cloud
et consulter l'onglet « Abonnement ».
</p>
<p>
Si vous avez besoin d'assistance concernant ODMS Cloud, veuillez
contacter $DEALER_NAME$.
</p>
<p>
Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail
de votre système.<br />
Il s'agit d'un e-mail généré automatiquement et cette boîte aux lettres
n'est pas surveillée. Merci de ne pas répondre.
</p>
</div>
</body>
</html>

View File

@ -0,0 +1,44 @@
<English>
Dear $CUSTOMER_NAME$,
The storage usage for your account has exceeded the usage limit. Functions related to the Dictation Workfrow will be restricted until the storage usage becomes lower than the limit.
Please remove Dictations files once the transcription is completed or add capacity by assigning a license to a new user. 5GB of storage will be provided to the account for each active user.
For detailed information, please sign in to ODMS Cloud and check the "Subscription" tab.
If you need support regarding ODMS Cloud, please contact $DEALER_NAME$.
If you have received this e-mail in error, please delete this e-mail from your system.
This is an automatically generated e-mail and this mailbox is not monitored. Please do not reply.
<Deutsch>
Sehr geehrte(r) $CUSTOMER_NAME$,
Die Speichernutzung Ihres Kontos hat das Nutzungslimit überschritten. Funktionen im Zusammenhang mit dem Dictation Workfrow werden eingeschränkt, bis die Speichernutzung unter den Grenzwert sinkt.
Bitte entfernen Sie Diktatdateien, sobald die Transkription abgeschlossen ist, oder erhöhen Sie die Kapazität, indem Sie einem neuen Benutzer eine Lizenz zuweisen. Für jeden aktiven Benutzer werden dem Konto 5 GB Speicherplatz zur Verfügung gestellt.
Für detaillierte Informationen melden Sie sich bitte bei ODMS Cloud an und überprüfen Sie die Registerkarte „Abonnement“.
Wenn Sie Unterstützung bezüglich ODMS Cloud benötigen, wenden Sie sich bitte an $DEALER_NAME$.
Wenn Sie diese E-Mail fälschlicherweise erhalten haben, löschen Sie diese E-Mail bitte aus Ihrem System.
Dies ist eine automatisch generierte E-Mail und dieses Postfach wird nicht überwacht. Bitte nicht antworten.
<Français>
Chère/Cher $CUSTOMER_NAME$,
L'utilisation du stockage pour votre compte a dépassé la limite d'utilisation. Les fonctions liées au Workfrow de dictée seront restreintes jusqu'à ce que l'utilisation du stockage devienne inférieure à la limite.
Veuillez supprimer les fichiers de dictées une fois la transcription terminée ou ajouter de la capacité en attribuant une licence à un nouvel utilisateur. 5 Go de stockage seront fournis au compte pour chaque utilisateur actif.
Pour des informations détaillées, veuillez vous connecter à ODMS Cloud et consulter l'onglet « Abonnement ».
Si vous avez besoin d'assistance concernant ODMS Cloud, veuillez contacter $DEALER_NAME$.
Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail de votre système.
Il s'agit d'un e-mail généré automatiquement et cette boîte aux lettres n'est pas surveillée. Merci de ne pas répondre.

View File

@ -0,0 +1,83 @@
<html>
<head>
<title>Storage Usage Exceeded Notification [U-119]</title>
</head>
<body>
<div>
<h3>&lt;English&gt;</h3>
<p>Dear $CUSTOMER_NAME$,</p>
<p>
The storage usage for your account has exceeded the usage limit.
Functions related to the Dictation Workfrow will be restricted until the
storage usage becomes lower than the limit.
</p>
<p>
Please remove Dictations files once the transcription is completed or
add capacity by assigning a license to a new user. 5GB of storage will
be provided to the account for each active user.
</p>
<p>
For detailed information, please sign in to ODMS Cloud and check the
"Subscription" tab.
</p>
<p>
If you have received this e-mail in error, please delete this e-mail
from your system.<br />
This is an automatically generated e-mail and this mailbox is not
monitored. Please do not reply.
</p>
</div>
<div>
<h3>&lt;Deutsch&gt;</h3>
<p>Sehr geehrte(r) $CUSTOMER_NAME$,</p>
<p>
Die Speichernutzung Ihres Kontos hat das Nutzungslimit überschritten.
Funktionen im Zusammenhang mit dem Dictation Workfrow werden
eingeschränkt, bis die Speichernutzung unter den Grenzwert sinkt.
</p>
<p>
Bitte entfernen Sie Diktatdateien, sobald die Transkription
abgeschlossen ist, oder erhöhen Sie die Kapazität, indem Sie einem neuen
Benutzer eine Lizenz zuweisen. Für jeden aktiven Benutzer werden dem
Konto 5 GB Speicherplatz zur Verfügung gestellt.
</p>
<p>
Für detaillierte Informationen melden Sie sich bitte bei ODMS Cloud an
und überprüfen Sie die Registerkarte „Abonnement“.
</p>
<p>
Wenn Sie diese E-Mail fälschlicherweise erhalten haben, löschen Sie
diese E-Mail bitte aus Ihrem System.<br />
Dies ist eine automatisch generierte E-Mail und dieses Postfach wird
nicht überwacht. Bitte nicht antworten.
</p>
</div>
<div>
<h3>&lt;Français&gt;</h3>
<p>Chère/Cher $CUSTOMER_NAME$,</p>
<p>
L'utilisation du stockage pour votre compte a dépassé la limite
d'utilisation. Les fonctions liées au Workfrow de dictée seront
restreintes jusqu'à ce que l'utilisation du stockage devienne inférieure
à la limite.
</p>
<p>
Veuillez supprimer les fichiers de dictées une fois la transcription
terminée ou ajouter de la capacité en attribuant une licence à un nouvel
utilisateur. 5 Go de stockage seront fournis au compte pour chaque
utilisateur actif.
</p>
<p>
Pour des informations détaillées, veuillez vous connecter à ODMS Cloud
et consulter l'onglet « Abonnement ».
</p>
<p>
Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail
de votre système.<br />
Il s'agit d'un e-mail généré automatiquement et cette boîte aux lettres
n'est pas surveillée. Merci de ne pas répondre.
</p>
</div>
</body>
</html>

View File

@ -0,0 +1,38 @@
<English>
Dear $CUSTOMER_NAME$,
The storage usage for your account has exceeded the usage limit. Functions related to the Dictation Workfrow will be restricted until the storage usage becomes lower than the limit.
Please remove Dictations files once the transcription is completed or add capacity by assigning a license to a new user. 5GB of storage will be provided to the account for each active user.
For detailed information, please sign in to ODMS Cloud and check the "Subscription" tab.
If you have received this e-mail in error, please delete this e-mail from your system.
This is an automatically generated e-mail and this mailbox is not monitored. Please do not reply.
<Deutsch>
Sehr geehrte(r) $CUSTOMER_NAME$,
Die Speichernutzung Ihres Kontos hat das Nutzungslimit überschritten. Funktionen im Zusammenhang mit dem Dictation Workfrow werden eingeschränkt, bis die Speichernutzung unter den Grenzwert sinkt.
Bitte entfernen Sie Diktatdateien, sobald die Transkription abgeschlossen ist, oder erhöhen Sie die Kapazität, indem Sie einem neuen Benutzer eine Lizenz zuweisen. Für jeden aktiven Benutzer werden dem Konto 5 GB Speicherplatz zur Verfügung gestellt.
Für detaillierte Informationen melden Sie sich bitte bei ODMS Cloud an und überprüfen Sie die Registerkarte „Abonnement“.
Wenn Sie diese E-Mail fälschlicherweise erhalten haben, löschen Sie diese E-Mail bitte aus Ihrem System.
Dies ist eine automatisch generierte E-Mail und dieses Postfach wird nicht überwacht. Bitte nicht antworten.
<Français>
Chère/Cher $CUSTOMER_NAME$,
L'utilisation du stockage pour votre compte a dépassé la limite d'utilisation. Les fonctions liées au Workfrow de dictée seront restreintes jusqu'à ce que l'utilisation du stockage devienne inférieure à la limite.
Veuillez supprimer les fichiers de dictées une fois la transcription terminée ou ajouter de la capacité en attribuant une licence à un nouvel utilisateur. 5 Go de stockage seront fournis au compte pour chaque utilisateur actif.
Pour des informations détaillées, veuillez vous connecter à ODMS Cloud et consulter l'onglet « Abonnement ».
Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail de votre système.
Il s'agit d'un e-mail généré automatiquement et cette boîte aux lettres n'est pas surveillée. Merci de ne pas répondre.