From 31de71f743a1ebd860172436b6f30621bf4091e8 Mon Sep 17 00:00:00 2001 From: "makabe.t" Date: Tue, 5 Mar 2024 10:27:50 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20797:=20API=E5=AE=9F=E8=A3=85?= =?UTF-8?q?=EF=BC=88=E4=B8=80=E6=8B=AC=E7=99=BB=E9=8C=B2=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3752: API実装(一括登録)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3752) - ユーザー一括登録APIとテストを実装しました。 - メール文面はまだ翻訳が来ていないので日本語のものを使用しています。別タスクで多言語対応します。 ## レビューポイント - ファイル名は認識通りでしょうか? - ファイルの内容は認識通りでしょうか? ## UIの変更 - なし ## 動作確認状況 - ローカルで確認 --- dictation_server/.env.test | 3 + dictation_server/src/common/test/overrides.ts | 11 + .../features/users/test/users.service.mock.ts | 3 + .../src/features/users/users.controller.ts | 5 +- .../src/features/users/users.module.ts | 2 + .../src/features/users/users.service.spec.ts | 201 ++++++++++++++++++ .../src/features/users/users.service.ts | 122 ++++++++++- .../blobstorage/blobstorage.service.ts | 50 +++++ .../src/gateways/sendgrid/sendgrid.service.ts | 89 ++++++++ dictation_server/src/templates/constants.ts | 1 - .../src/templates/template_U_120.html | 80 +++++++ .../src/templates/template_U_120.txt | 57 +++++ .../templates/template_U_120_no_parent.html | 68 ++++++ .../templates/template_U_120_no_parent.txt | 51 +++++ 14 files changed, 734 insertions(+), 9 deletions(-) create mode 100644 dictation_server/src/templates/template_U_120.html create mode 100644 dictation_server/src/templates/template_U_120.txt create mode 100644 dictation_server/src/templates/template_U_120_no_parent.html create mode 100644 dictation_server/src/templates/template_U_120_no_parent.txt diff --git a/dictation_server/.env.test b/dictation_server/.env.test index a2748df..81bb261 100644 --- a/dictation_server/.env.test +++ b/dictation_server/.env.test @@ -20,12 +20,15 @@ STORAGE_TOKEN_EXPIRE_TIME=30 STORAGE_ACCOUNT_NAME_US=saxxxxusxxx STORAGE_ACCOUNT_NAME_AU=saxxxxauxxx STORAGE_ACCOUNT_NAME_EU=saxxxxeuxxx +STORAGE_ACCOUNT_NAME_IMPORTS=saxxxximportsxxx STORAGE_ACCOUNT_KEY_US=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX== STORAGE_ACCOUNT_KEY_AU=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX== STORAGE_ACCOUNT_KEY_EU=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX== +STORAGE_ACCOUNT_KEY_IMPORTS=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX== STORAGE_ACCOUNT_ENDPOINT_US=https://xxxxxxxxxxxx.blob.core.windows.net/ STORAGE_ACCOUNT_ENDPOINT_AU=https://xxxxxxxxxxxx.blob.core.windows.net/ STORAGE_ACCOUNT_ENDPOINT_EU=https://xxxxxxxxxxxx.blob.core.windows.net/ +STORAGE_ACCOUNT_ENDPOINT_IMPORTS=https://xxxxxxxxxxxx.blob.core.windows.net/ ACCESS_TOKEN_LIFETIME_WEB=7200000 REFRESH_TOKEN_LIFETIME_WEB=86400000 REFRESH_TOKEN_LIFETIME_DEFAULT=2592000000 diff --git a/dictation_server/src/common/test/overrides.ts b/dictation_server/src/common/test/overrides.ts index 6bb9aea..4c65c7c 100644 --- a/dictation_server/src/common/test/overrides.ts +++ b/dictation_server/src/common/test/overrides.ts @@ -180,6 +180,11 @@ export const overrideBlobstorageService = ( accountId: number, country: string, ) => Promise; + uploadImportsBlob?: ( + context: Context, + fileName: string, + content: string, + ) => Promise; }, ): void => { // テストコードでのみ許される強引な方法でprivateメンバ変数の参照を取得 @@ -220,6 +225,12 @@ export const overrideBlobstorageService = ( writable: true, }); } + if (overrides.uploadImportsBlob) { + Object.defineProperty(obj, obj.uploadImportsBlob.name, { + value: overrides.uploadImportsBlob, + writable: true, + }); + } }; /** diff --git a/dictation_server/src/features/users/test/users.service.mock.ts b/dictation_server/src/features/users/test/users.service.mock.ts index c2da3db..f17d58e 100644 --- a/dictation_server/src/features/users/test/users.service.mock.ts +++ b/dictation_server/src/features/users/test/users.service.mock.ts @@ -19,6 +19,7 @@ import { import { AdB2cUser } from '../../../gateways/adb2c/types/types'; import { ADB2C_SIGN_IN_TYPE } from '../../../constants'; import { AccountsRepositoryService } from '../../../repositories/accounts/accounts.repository.service'; +import { BlobstorageService } from '../../../gateways/blobstorage/blobstorage.service'; export type SortCriteriaRepositoryMockValue = { updateSortCriteria: SortCriteria | Error; @@ -89,6 +90,8 @@ export const makeUsersServiceMock = async ( return makeSortCriteriaRepositoryMock( sortCriteriaRepositoryMockValue, ); + case BlobstorageService: + return {}; } }) .compile(); diff --git a/dictation_server/src/features/users/users.controller.ts b/dictation_server/src/features/users/users.controller.ts index 531c13b..7d731cc 100644 --- a/dictation_server/src/features/users/users.controller.ts +++ b/dictation_server/src/features/users/users.controller.ts @@ -4,7 +4,6 @@ import { Get, HttpException, HttpStatus, - Ip, Logger, Post, Query, @@ -1068,7 +1067,9 @@ export class UsersController { const context = makeContext(userId, requestId, delegateUserId); this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`); - // TODO: 処理を実装 + // 登録処理 + const { users, filename } = body; + await this.usersService.multipleImports(context, userId, filename, users); return {}; } diff --git a/dictation_server/src/features/users/users.module.ts b/dictation_server/src/features/users/users.module.ts index b5f5963..f8d02d9 100644 --- a/dictation_server/src/features/users/users.module.ts +++ b/dictation_server/src/features/users/users.module.ts @@ -9,6 +9,7 @@ import { UsersController } from './users.controller'; import { UsersService } from './users.service'; import { AuthService } from '../auth/auth.service'; import { AccountsRepositoryModule } from '../../repositories/accounts/accounts.repository.module'; +import { BlobstorageModule } from '../../gateways/blobstorage/blobstorage.module'; @Module({ imports: [ @@ -19,6 +20,7 @@ import { AccountsRepositoryModule } from '../../repositories/accounts/accounts.r AdB2cModule, SendGridModule, ConfigModule, + BlobstorageModule, ], controllers: [UsersController], providers: [UsersService, AuthService], diff --git a/dictation_server/src/features/users/users.service.spec.ts b/dictation_server/src/features/users/users.service.spec.ts index 26f9703..503329d 100644 --- a/dictation_server/src/features/users/users.service.spec.ts +++ b/dictation_server/src/features/users/users.service.spec.ts @@ -31,6 +31,7 @@ import { makeTestingModule } from '../../common/test/modules'; import { Context, makeContext } from '../../common/log'; import { overrideAdB2cService, + overrideBlobstorageService, overrideSendgridService, overrideUsersRepositoryService, } from '../../common/test/overrides'; @@ -3656,6 +3657,206 @@ describe('UsersService.deleteUser', () => { }); }); +describe('UsersService.multipleImports', () => { + 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が行われるため注意 + }); + 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: dealer } = await makeTestAccount(source, { + company_name: 'dealerCompany', + tier: 4, + }); + const { account, admin } = await makeTestAccount(source, { + tier: 5, + company_name: 'company', + parent_account_id: dealer.id, + }); + + const service = module.get(UsersService); + const context = makeContext(`uuidv4`, 'requestId'); + + overrideAdB2cService(service, { + getUsers: async () => { + return [ + { + id: admin.external_id, + displayName: 'admin', + identities: [ + { + signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + issuer: 'issuer', + issuerAssignedId: 'admin@example.com', + }, + ], + }, + ]; + }, + }); + overrideSendgridService(service, {}); + overrideBlobstorageService(service, { + uploadImportsBlob: async () => {}, + }); + + // メール送信関数が呼ばれたかどうかで判定を行う。実際のメール送信は行わない。 + const spy = jest + .spyOn(service['sendgridService'], 'sendMailWithU120') + .mockImplementation(); + + const now = new Date(); + const date = `${now.getFullYear()}.${now.getMonth() + 1}.${now.getDate()}`; + const filename = 'fileName.csv'; + + const users = [ + { + name: 'name1', + email: 'mail1@example.com', + role: 1, + authorId: 'HOGE1', + autoRenew: 0, + notification: 0, + encryption: 0, + encryptionPassword: 'string', + prompt: 0, + }, + { + name: 'name2', + email: 'mail2@example.com', + role: 2, + autoRenew: 0, + notification: 0, + }, + ]; + await service.multipleImports(context, admin.external_id, filename, users); + + // メール送信メソッドが呼ばれているか確認 + expect(spy).toHaveBeenCalledWith( + context, + ['admin@example.com'], + account.company_name, + dealer.company_name, + date, + filename, + ); + }); + + it('Blobアップロードに失敗した場合はエラーとなること', async () => { + if (!source) fail(); + const module = await makeTestingModule(source); + if (!module) fail(); + + const { account: dealer } = await makeTestAccount(source, { + company_name: 'dealerCompany', + tier: 4, + }); + const { account, admin } = await makeTestAccount(source, { + tier: 5, + company_name: 'company', + parent_account_id: dealer.id, + }); + + const service = module.get(UsersService); + const context = makeContext(`uuidv4`, 'requestId'); + + overrideAdB2cService(service, { + getUsers: async () => { + return [ + { + id: admin.external_id, + displayName: 'admin', + identities: [ + { + signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + issuer: 'issuer', + issuerAssignedId: 'admin@example.com', + }, + ], + }, + ]; + }, + }); + overrideSendgridService(service, {}); + overrideBlobstorageService(service, { + uploadImportsBlob: async () => { + throw new Error('error'); + }, + }); + + const now = new Date(); + const date = `${now.getFullYear()}.${now.getMonth() + 1}.${now.getDate()}`; + const filename = 'fileName.csv'; + + const users = [ + { + name: 'name1', + email: 'mail1@example.com', + role: 1, + authorId: 'HOGE1', + autoRenew: 0, + notification: 0, + encryption: 0, + encryptionPassword: 'string', + prompt: 0, + }, + { + name: 'name2', + email: 'mail2@example.com', + role: 2, + autoRenew: 0, + notification: 0, + }, + ]; + + try { + await service.multipleImports( + context, + admin.external_id, + filename, + users, + ); + fail(); + } catch (e) { + if (e instanceof HttpException) { + expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR); + expect(e.getResponse()).toEqual(makeErrorResponse('E009999')); + } else { + fail(); + } + } + }); +}); + describe('UsersService.multipleImportsComplate', () => { let source: DataSource | null = null; diff --git a/dictation_server/src/features/users/users.service.ts b/dictation_server/src/features/users/users.service.ts index 2dbf943..2df3703 100644 --- a/dictation_server/src/features/users/users.service.ts +++ b/dictation_server/src/features/users/users.service.ts @@ -24,6 +24,7 @@ import { import { UsersRepositoryService } from '../../repositories/users/users.repository.service'; import { LicensesRepositoryService } from '../../repositories/licenses/licenses.repository.service'; import { + MultipleImportUser, GetRelationsResponse, MultipleImportErrors, User, @@ -63,12 +64,11 @@ import { AccountNotFoundError } from '../../repositories/accounts/errors/types'; import { getUserNameAndMailAddress } from '../../gateways/adb2c/utils/utils'; import { AccountsRepositoryService } from '../../repositories/accounts/accounts.repository.service'; import { Account } from '../../repositories/accounts/entity/account.entity'; +import { BlobstorageService } from '../../gateways/blobstorage/blobstorage.service'; @Injectable() export class UsersService { private readonly logger = new Logger(UsersService.name); - private readonly mailFrom: string; - private readonly appDomain: string; constructor( private readonly accountsRepository: AccountsRepositoryService, private readonly usersRepository: UsersRepositoryService, @@ -77,10 +77,8 @@ export class UsersService { private readonly adB2cService: AdB2cService, private readonly configService: ConfigService, private readonly sendgridService: SendGridService, - ) { - this.mailFrom = this.configService.getOrThrow('MAIL_FROM'); - this.appDomain = this.configService.getOrThrow('APP_DOMAIN'); - } + private readonly blobStorageService: BlobstorageService, + ) {} /** * Confirms user @@ -1600,6 +1598,118 @@ export class UsersService { } } + /** + * ユーザー一括登録用のファイルをBlobにアップロードする + * @param context + * @param externalId + * @param fileName + * @param users + * @returns imports + */ + async multipleImports( + context: Context, + externalId: string, + fileName: string, + users: MultipleImportUser[], + ): Promise { + this.logger.log( + `[IN] [${context.getTrackingId()}] ${ + this.multipleImports.name + } | params: { externalId: ${externalId}, ` + + `fileName: ${fileName}, ` + + `users.length: ${users.length} };`, + ); + try { + // ユーザー情報を取得 + const user = await this.usersRepository.findUserByExternalId( + context, + externalId, + ); + + if (user == null) { + throw new Error(`user not found. externalId=${externalId}`); + } + + const now = new Date(); + // 日時を生成(YYYYMMDD_HHMMSS) + const dateTime = + `${now.getFullYear().toString().padStart(4, '0')}` + + `${(now.getMonth() + 1).toString().padStart(2, '0')}` + // 月は0から始まるため+1する + `${now.getDate().toString().padStart(2, '0')}` + + `_${now.getHours().toString().padStart(2, '0')}` + + `${now.getMinutes().toString().padStart(2, '0')}` + + `${now.getSeconds().toString().padStart(2, '0')}`; + + // ファイル名を生成(U_YYYYMMDD_HHMMSS_アカウントID_ユーザーID.json) + const jsonFileName = `U_${dateTime}_${user.account_id}_${user.id}.json`; + + // ユーザー情報をJSON形式に変換 + const usersJson = JSON.stringify({ + account_id: user.account_id, + user_id: user.id, + user_role: user.role, + external_id: user.external_id, + file_name: fileName, + date: Math.floor(now.getTime() / 1000), + data: users, + }); + + // Blobにファイルをアップロード(ユーザー一括登録用) + await this.blobStorageService.uploadImportsBlob( + context, + jsonFileName, + usersJson, + ); + + // 受付完了メールを送信 + try { + // アカウント・管理者情報を取得 + const { adminEmails, companyName } = await this.getAccountInformation( + context, + user.account_id, + ); + + // Dealer情報を取得 + const dealer = await this.accountsRepository.findParentAccount( + context, + user.account_id, + ); + const dealerName = dealer?.company_name ?? null; + + const now = new Date(); + // 日時を生成(YYYY.MM.DD) + const date = `${now.getFullYear()}.${ + now.getMonth() + 1 // 月は0から始まるため+1する + }.${now.getDate()}`; + + await this.sendgridService.sendMailWithU120( + context, + adminEmails, + companyName, + dealerName, + date, + fileName, + ); + } catch (e) { + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + // メール送信に関する例外はログだけ出して握りつぶす + } + } catch (e) { + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + switch (e.constructor) { + default: + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } finally { + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${this.multipleImports.name}`, + ); + } + } + /** * アカウントIDを指定して、アカウント情報と管理者情報を取得する * @param context diff --git a/dictation_server/src/gateways/blobstorage/blobstorage.service.ts b/dictation_server/src/gateways/blobstorage/blobstorage.service.ts index 9199ad9..baaf57e 100644 --- a/dictation_server/src/gateways/blobstorage/blobstorage.service.ts +++ b/dictation_server/src/gateways/blobstorage/blobstorage.service.ts @@ -22,9 +22,11 @@ export class BlobstorageService { private readonly blobServiceClientUS: BlobServiceClient; private readonly blobServiceClientEU: BlobServiceClient; private readonly blobServiceClientAU: BlobServiceClient; + private readonly blobServiceClientImports: BlobServiceClient; private readonly sharedKeyCredentialUS: StorageSharedKeyCredential; private readonly sharedKeyCredentialAU: StorageSharedKeyCredential; private readonly sharedKeyCredentialEU: StorageSharedKeyCredential; + private readonly sharedKeyCredentialImports: StorageSharedKeyCredential; private readonly sasTokenExpireHour: number; constructor(private readonly configService: ConfigService) { this.sharedKeyCredentialUS = new StorageSharedKeyCredential( @@ -39,6 +41,11 @@ export class BlobstorageService { this.configService.getOrThrow('STORAGE_ACCOUNT_NAME_EU'), this.configService.getOrThrow('STORAGE_ACCOUNT_KEY_EU'), ); + this.sharedKeyCredentialImports = new StorageSharedKeyCredential( + this.configService.getOrThrow('STORAGE_ACCOUNT_NAME_IMPORTS'), + this.configService.getOrThrow('STORAGE_ACCOUNT_KEY_IMPORTS'), + ); + this.blobServiceClientUS = new BlobServiceClient( this.configService.getOrThrow('STORAGE_ACCOUNT_ENDPOINT_US'), this.sharedKeyCredentialUS, @@ -51,6 +58,10 @@ export class BlobstorageService { this.configService.getOrThrow('STORAGE_ACCOUNT_ENDPOINT_EU'), this.sharedKeyCredentialEU, ); + this.blobServiceClientImports = new BlobServiceClient( + this.configService.getOrThrow('STORAGE_ACCOUNT_ENDPOINT_IMPORTS'), + this.sharedKeyCredentialImports, + ); this.sasTokenExpireHour = this.configService.getOrThrow( 'STORAGE_TOKEN_EXPIRE_TIME', ); @@ -457,6 +468,45 @@ export class BlobstorageService { return url.toString(); } + /** + * 一括登録用のBlobを作成します + * @param context + * @param fileName + * @param content + * @returns imports blob + */ + async uploadImportsBlob( + context: Context, + fileName: string, + content: string, + ): Promise { + this.logger.log( + `[IN] [${context.getTrackingId()}] ${ + this.uploadImportsBlob.name + } | params: { fileName: ${fileName} };`, + ); + + try { + const containerClient = + this.blobServiceClientImports.getContainerClient('import-users'); + const blockBlobClient = containerClient.getBlockBlobClient(fileName); + const result = await blockBlobClient.upload(content, content.length); + + if (result.errorCode) { + throw new Error( + `upload failed. errorCode: ${result.errorCode} fileName: ${fileName}`, + ); + } + } catch (e) { + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + throw e; + } finally { + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${this.uploadImportsBlob.name}`, + ); + } + } + /** * Gets container client * @param companyName diff --git a/dictation_server/src/gateways/sendgrid/sendgrid.service.ts b/dictation_server/src/gateways/sendgrid/sendgrid.service.ts index bfc1064..e960d25 100644 --- a/dictation_server/src/gateways/sendgrid/sendgrid.service.ts +++ b/dictation_server/src/gateways/sendgrid/sendgrid.service.ts @@ -75,6 +75,10 @@ export class SendGridService { private readonly templateU119Text: string; private readonly templateU119NoParentHtml: string; private readonly templateU119NoParentText: string; + private readonly templateU120Html: string; + private readonly templateU120Text: string; + private readonly templateU120NoParentHtml: string; + private readonly templateU120NoParentText: string; private readonly templateU121Html: string; private readonly templateU121Text: string; private readonly templateU121NoParentHtml: string; @@ -267,6 +271,25 @@ export class SendGridService { path.resolve(__dirname, `../../templates/template_U_119_no_parent.txt`), 'utf-8', ); + this.templateU120Html = readFileSync( + path.resolve(__dirname, `../../templates/template_U_120.html`), + 'utf-8', + ); + this.templateU120Text = readFileSync( + path.resolve(__dirname, `../../templates/template_U_120.txt`), + 'utf-8', + ); + this.templateU120NoParentHtml = readFileSync( + path.resolve( + __dirname, + `../../templates/template_U_120_no_parent.html`, + ), + 'utf-8', + ); + this.templateU120NoParentText = readFileSync( + path.resolve(__dirname, `../../templates/template_U_120_no_parent.txt`), + 'utf-8', + ); this.templateU121Html = readFileSync( path.resolve(__dirname, `../../templates/template_U_121.html`), 'utf-8', @@ -1188,6 +1211,72 @@ export class SendGridService { } } + /** + * U-120のテンプレートを使用したメールを送信する + * @param context + * @param customerAdminMails アカウントの管理者(primary/secondary)のメールアドレス + * @param customerAccountName アカウントの名前 + * @param dealerAccountName 問題発生時に問い合わせする先の上位のディーラー名(会社名) + * @param tire1AdminMails 第一階層の管理者(primary/secondary)のメールアドレス + * @returns mail with u119 + */ + async sendMailWithU120( + context: Context, + customerAdminMails: string[], + customerAccountName: string, + dealerAccountName: string | null, + requestTime: string, + fileName: string, + ): Promise { + this.logger.log( + `[IN] [${context.getTrackingId()}] ${this.sendMailWithU120.name}`, + ); + try { + const subject = 'ユーザー一括登録 受付通知 [U-120]'; + + let html: string; + let text: string; + console.log(dealerAccountName); + + if (!dealerAccountName) { + html = this.templateU120NoParentHtml + .replaceAll(CUSTOMER_NAME, customerAccountName) + .replaceAll(REQUEST_TIME, requestTime) + .replaceAll(FILE_NAME, fileName); + text = this.templateU120NoParentText + .replaceAll(CUSTOMER_NAME, customerAccountName) + .replaceAll(REQUEST_TIME, requestTime) + .replaceAll(FILE_NAME, fileName); + } else { + html = this.templateU120Html + .replaceAll(CUSTOMER_NAME, customerAccountName) + .replaceAll(DEALER_NAME, dealerAccountName) + .replaceAll(REQUEST_TIME, requestTime) + .replaceAll(FILE_NAME, fileName); + text = this.templateU120Text + .replaceAll(CUSTOMER_NAME, customerAccountName) + .replaceAll(DEALER_NAME, dealerAccountName) + .replaceAll(REQUEST_TIME, requestTime) + .replaceAll(FILE_NAME, fileName); + } + + // メールを送信する + await this.sendMail( + context, + customerAdminMails, + [], + this.mailFrom, + subject, + text, + html, + ); + } finally { + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${this.sendMailWithU120.name}`, + ); + } + } + /** * U-121のテンプレートを使用したメールを送信する * @param context diff --git a/dictation_server/src/templates/constants.ts b/dictation_server/src/templates/constants.ts index 319bed8..d6650a6 100644 --- a/dictation_server/src/templates/constants.ts +++ b/dictation_server/src/templates/constants.ts @@ -12,7 +12,6 @@ export const FILE_NAME = '$FILE_NAME$'; export const TYPIST_NAME = '$TYPIST_NAME$'; export const TEMPORARY_PASSWORD = '$TEMPORARY_PASSWORD$'; export const REQUEST_TIME = '$REQUEST_TIME$'; - export const EMAIL_DUPLICATION = `$EMAIL_DUPLICATION$`; export const AUTHOR_ID_DUPLICATION = `$AUTHOR_ID_DUPLICATION$`; export const UNEXPECTED_ERROR = `$UNEXPECTED_ERROR$`; diff --git a/dictation_server/src/templates/template_U_120.html b/dictation_server/src/templates/template_U_120.html new file mode 100644 index 0000000..b4b4e75 --- /dev/null +++ b/dictation_server/src/templates/template_U_120.html @@ -0,0 +1,80 @@ + + + ユーザー一括登録 受付通知 [U-120] + + + +
+

<English>

+

Dear $CUSTOMER_NAME$,

+

ODMS Cloudをご利用いただきありがとうございます。

+

+ CSVファイルによるユーザー一括登録を受け付けました。
+ - リクエスト日時:$REQUEST_TIME$
+ - SCVファイル名:$FILE_NAME$ +

+

+ ・登録完了には時間がかかる場合がありますので少々お待ちください。
+ ・登録完了通知は別途お送りします。
+ ・CSVファイルの内容に間違いがある場合は登録を完了できません。 +

+

+ ディーラーによるサポートが必要な場合は、 $DEALER_NAME$ + にお問い合わせください。 +

+

+ If you received this e-mail in error, please delete this e-mail from + your system.
+ This is an automatically generated e-mail, please do not reply. +

+
+
+

<Deutsch>

+

Dear $CUSTOMER_NAME$,

+

ODMS Cloudをご利用いただきありがとうございます。

+

+ CSVファイルによるユーザー一括登録を受け付けました。
+ - リクエスト日時:$REQUEST_TIME$
+ - SCVファイル名:$FILE_NAME$ +

+

+ ・登録完了には時間がかかる場合がありますので少々お待ちください。
+ ・登録完了通知は別途お送りします。
+ ・CSVファイルの内容に間違いがある場合は登録を完了できません。 +

+

+ ディーラーによるサポートが必要な場合は、 $DEALER_NAME$ + にお問い合わせください。 +

+

+ If you received this e-mail in error, please delete this e-mail from + your system.
+ This is an automatically generated e-mail, please do not reply. +

+
+
+

<Français>

+

Dear $CUSTOMER_NAME$,

+

ODMS Cloudをご利用いただきありがとうございます。

+

+ CSVファイルによるユーザー一括登録を受け付けました。
+ - リクエスト日時:$REQUEST_TIME$
+ - SCVファイル名:$FILE_NAME$ +

+

+ ・登録完了には時間がかかる場合がありますので少々お待ちください。
+ ・登録完了通知は別途お送りします。
+ ・CSVファイルの内容に間違いがある場合は登録を完了できません。 +

+

+ ディーラーによるサポートが必要な場合は、 $DEALER_NAME$ + にお問い合わせください。 +

+

+ If you received this e-mail in error, please delete this e-mail from + your system.
+ This is an automatically generated e-mail, please do not reply. +

+
+ + diff --git a/dictation_server/src/templates/template_U_120.txt b/dictation_server/src/templates/template_U_120.txt new file mode 100644 index 0000000..e8c68ec --- /dev/null +++ b/dictation_server/src/templates/template_U_120.txt @@ -0,0 +1,57 @@ + + +Dear $CUSTOMER_NAME$, + +ODMS Cloudをご利用いただきありがとうございます。 + +CSVファイルによるユーザー一括登録を受け付けました。 + - リクエスト日時:$REQUEST_TIME$ + - SCVファイル名:$FILE_NAME$ + +・登録完了には時間がかかる場合がありますので少々お待ちください。 +・登録完了通知は別途お送りします。 +・CSVファイルの内容に間違いがある場合は登録を完了できません。 + +ディーラーによるサポートが必要な場合は、 $DEALER_NAME$ にお問い合わせください。 + +If you received this e-mail in error, please delete this e-mail from your system. +This is an automatically generated e-mail, please do not reply. + + + + +Dear $CUSTOMER_NAME$, + +ODMS Cloudをご利用いただきありがとうございます。 + +CSVファイルによるユーザー一括登録を受け付けました。 + - リクエスト日時:$REQUEST_TIME$ + - SCVファイル名:$FILE_NAME$ + +・登録完了には時間がかかる場合がありますので少々お待ちください。 +・登録完了通知は別途お送りします。 +・CSVファイルの内容に間違いがある場合は登録を完了できません。 + +ディーラーによるサポートが必要な場合は、 $DEALER_NAME$ にお問い合わせください。 + +If you received this e-mail in error, please delete this e-mail from your system. +This is an automatically generated e-mail, please do not reply. + + + +Dear $CUSTOMER_NAME$, + +ODMS Cloudをご利用いただきありがとうございます。 + +CSVファイルによるユーザー一括登録を受け付けました。 + - リクエスト日時:$REQUEST_TIME$ + - SCVファイル名:$FILE_NAME$ + +・登録完了には時間がかかる場合がありますので少々お待ちください。 +・登録完了通知は別途お送りします。 +・CSVファイルの内容に間違いがある場合は登録を完了できません。 + +ディーラーによるサポートが必要な場合は、 $DEALER_NAME$ にお問い合わせください。 + +If you received this e-mail in error, please delete this e-mail from your system. +This is an automatically generated e-mail, please do not reply. \ No newline at end of file diff --git a/dictation_server/src/templates/template_U_120_no_parent.html b/dictation_server/src/templates/template_U_120_no_parent.html new file mode 100644 index 0000000..692411d --- /dev/null +++ b/dictation_server/src/templates/template_U_120_no_parent.html @@ -0,0 +1,68 @@ + + + ユーザー一括登録 受付通知 [U-120] + + + +
+

<English>

+

Dear $CUSTOMER_NAME$,

+

ODMS Cloudをご利用いただきありがとうございます。

+

+ CSVファイルによるユーザー一括登録を受け付けました。
+ - リクエスト日時:$REQUEST_TIME$
+ - SCVファイル名:$FILE_NAME$ +

+

+ ・登録完了には時間がかかる場合がありますので少々お待ちください。
+ ・登録完了通知は別途お送りします。
+ ・CSVファイルの内容に間違いがある場合は登録を完了できません。 +

+

+ If you received this e-mail in error, please delete this e-mail from + your system.
+ This is an automatically generated e-mail, please do not reply. +

+
+
+

<Deutsch>

+

Dear $CUSTOMER_NAME$,

+

ODMS Cloudをご利用いただきありがとうございます。

+

+ CSVファイルによるユーザー一括登録を受け付けました。
+ - リクエスト日時:$REQUEST_TIME$
+ - SCVファイル名:$FILE_NAME$ +

+

+ ・登録完了には時間がかかる場合がありますので少々お待ちください。
+ ・登録完了通知は別途お送りします。
+ ・CSVファイルの内容に間違いがある場合は登録を完了できません。 +

+

+ If you received this e-mail in error, please delete this e-mail from + your system.
+ This is an automatically generated e-mail, please do not reply. +

+
+
+

<Français>

+

Dear $CUSTOMER_NAME$,

+

ODMS Cloudをご利用いただきありがとうございます。

+

+ CSVファイルによるユーザー一括登録を受け付けました。
+ - リクエスト日時:$REQUEST_TIME$
+ - SCVファイル名:$FILE_NAME$ +

+

+ ・登録完了には時間がかかる場合がありますので少々お待ちください。
+ ・登録完了通知は別途お送りします。
+ ・CSVファイルの内容に間違いがある場合は登録を完了できません。 +

+

+ If you received this e-mail in error, please delete this e-mail from + your system.
+ This is an automatically generated e-mail, please do not reply. +

+
+ + diff --git a/dictation_server/src/templates/template_U_120_no_parent.txt b/dictation_server/src/templates/template_U_120_no_parent.txt new file mode 100644 index 0000000..fca8bcf --- /dev/null +++ b/dictation_server/src/templates/template_U_120_no_parent.txt @@ -0,0 +1,51 @@ + + +Dear $CUSTOMER_NAME$, + +ODMS Cloudをご利用いただきありがとうございます。 + +CSVファイルによるユーザー一括登録を受け付けました。 + - リクエスト日時:$REQUEST_TIME$ + - SCVファイル名:$FILE_NAME$ + +・登録完了には時間がかかる場合がありますので少々お待ちください。 +・登録完了通知は別途お送りします。 +・CSVファイルの内容に間違いがある場合は登録を完了できません。 + +If you received this e-mail in error, please delete this e-mail from your system. +This is an automatically generated e-mail, please do not reply. + + + + +Dear $CUSTOMER_NAME$, + +ODMS Cloudをご利用いただきありがとうございます。 + +CSVファイルによるユーザー一括登録を受け付けました。 + - リクエスト日時:$REQUEST_TIME$ + - SCVファイル名:$FILE_NAME$ + +・登録完了には時間がかかる場合がありますので少々お待ちください。 +・登録完了通知は別途お送りします。 +・CSVファイルの内容に間違いがある場合は登録を完了できません。 + +If you received this e-mail in error, please delete this e-mail from your system. +This is an automatically generated e-mail, please do not reply. + + + +Dear $CUSTOMER_NAME$, + +ODMS Cloudをご利用いただきありがとうございます。 + +CSVファイルによるユーザー一括登録を受け付けました。 + - リクエスト日時:$REQUEST_TIME$ + - SCVファイル名:$FILE_NAME$ + +・登録完了には時間がかかる場合がありますので少々お待ちください。 +・登録完了通知は別途お送りします。 +・CSVファイルの内容に間違いがある場合は登録を完了できません。 + +If you received this e-mail in error, please delete this e-mail from your system. +This is an automatically generated e-mail, please do not reply. \ No newline at end of file