diff --git a/dictation_server/src/common/error/code.ts b/dictation_server/src/common/error/code.ts index 00d1c62..8bd934c 100644 --- a/dictation_server/src/common/error/code.ts +++ b/dictation_server/src/common/error/code.ts @@ -88,4 +88,5 @@ export const ErrorCodes = [ 'E017004', // 親アカウント変更不可エラー(国が同一でない) 'E018001', // パートナーアカウント削除エラー(削除条件を満たしていない) 'E019001', // パートナーアカウント取得不可エラー(階層構造が不正) + 'E020001', // パートナーアカウント変更エラー(変更条件を満たしていない) ] as const; diff --git a/dictation_server/src/common/error/message.ts b/dictation_server/src/common/error/message.ts index 123dc5c..d384512 100644 --- a/dictation_server/src/common/error/message.ts +++ b/dictation_server/src/common/error/message.ts @@ -78,4 +78,5 @@ export const errors: Errors = { E017004: 'Parent account switch failed Error: country mismatch', E018001: 'Partner account delete failed Error: not satisfied conditions', E019001: 'Partner account get failed Error: hierarchy mismatch', + E020001: 'Partner account change failed Error: not satisfied conditions', }; diff --git a/dictation_server/src/features/accounts/accounts.controller.ts b/dictation_server/src/features/accounts/accounts.controller.ts index 0cbee34..3523dc9 100644 --- a/dictation_server/src/features/accounts/accounts.controller.ts +++ b/dictation_server/src/features/accounts/accounts.controller.ts @@ -2602,7 +2602,7 @@ export class AccountsController { @Req() req: Request, @Body() body: UpdatePartnerInfoRequest, ): Promise { - const { targetAccountId } = body; + const { targetAccountId, primaryAdminUserId, companyName } = body; const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { @@ -2639,12 +2639,14 @@ export class AccountsController { const context = makeContext(userId, requestId); this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`); - // TODO: 仮実装 - /*await this.accountService.updatePartnerAccount( + await this.accountService.updatePartnerInfo( context, + userId, targetAccountId, + primaryAdminUserId, + companyName, ); - */ + return {}; } } diff --git a/dictation_server/src/features/accounts/accounts.service.spec.ts b/dictation_server/src/features/accounts/accounts.service.spec.ts index 066eb0b..cd50a2f 100644 --- a/dictation_server/src/features/accounts/accounts.service.spec.ts +++ b/dictation_server/src/features/accounts/accounts.service.spec.ts @@ -9635,3 +9635,492 @@ describe('getPartnerUsers', () => { } }); }); + +describe('updatePartnerInfo', () => { + let source: DataSource | null = null; + beforeAll(async () => { + if (source == null) { + source = await (async () => { + const s = new DataSource({ + type: 'mysql', + host: 'test_mysql_db', + port: 3306, + username: 'user', + password: 'password', + database: 'odms', + entities: [__dirname + '/../../**/*.entity{.ts,.js}'], + synchronize: false, // trueにすると自動的にmigrationが行われるため注意 + logger: new TestLogger('none'), + logging: true, + }); + return await s.initialize(); + })(); + } + }); + + beforeEach(async () => { + if (source) { + await truncateAllTable(source); + } + }); + + afterAll(async () => { + await source?.destroy(); + source = null; + }); + + it('パートナーアカウントの会社名を変更できる', async () => { + if (!source) fail(); + const module = await makeTestingModule(source); + if (!module) fail(); + // 新規親アカウントのアカウントを作成する + const { account: parent, admin: parentAdmin } = await makeTestAccount( + source, + { tier: 3 }, + { external_id: 'parent_external_id' }, + ); + + // 子アカウントを作成する + const { account: partner, admin: partnerAdmin } = await makeTestAccount( + source, + { + tier: 4, + parent_account_id: parent.id, + company_name: 'oldCompanyName', + }, + ); + + // 作成したデータを確認 + { + const partnerRecord = await getAccount(source, partner.id); + expect(partnerRecord?.company_name).toBe('oldCompanyName'); + } + + const service = module.get(AccountsService); + let _subject = ''; + overrideSendgridService(service, { + sendMail: async ( + context: Context, + to: string[], + cc: string[], + from: string, + subject: string, + text: string, + html: string, + ) => { + _subject = subject; + }, + }); + overrideAdB2cService(service, { + getUser: async (context, externalId) => { + return { + displayName: 'adb2c' + externalId, + id: externalId, + identities: [ + { + signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + issuer: 'xxxxxx', + issuerAssignedId: 'mail@example.com', + }, + ], + }; + }, + getUsers: async (context, externalIds) => + externalIds.map((externalId) => { + return { + displayName: 'adb2c' + externalId, + id: externalId, + identities: [ + { + signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + issuer: 'xxxxxx', + issuerAssignedId: 'mail@example.com', + }, + ], + }; + }), + }); + + // テスト実行 + const context = makeContext(parentAdmin.external_id, 'requestId'); + + await service.updatePartnerInfo( + context, + parentAdmin.external_id, + partner.id, + partnerAdmin.id, + 'newCompanyName', + ); + + { + // DB内が想定通りになっているか確認 + const partnerRecord = await getAccount(source, partner.id); + expect(partnerRecord?.company_name).toBe('newCompanyName'); + + // パートナーアカウント情報変更完了通知が送信されていること + expect(_subject).toBe('Partner Account Edit Notification [U-124]'); + } + }); + + it('パートナーアカウントのプライマリ管理者を変更できる', async () => { + if (!source) fail(); + const module = await makeTestingModule(source); + if (!module) fail(); + // 新規親アカウントのアカウントを作成する + const { account: parent, admin: parentAdmin } = await makeTestAccount( + source, + { tier: 3 }, + { external_id: 'parent_external_id' }, + ); + + // 子アカウントを作成する + const { account: partner, admin: partnerAdmin } = await makeTestAccount( + source, + { + tier: 4, + parent_account_id: parent.id, + }, + ); + const newPartnerAdmin = await makeTestUser(source, { + account_id: partner.id, + }); + + // 作成したデータを確認 + { + const partnerRecord = await getAccount(source, partner.id); + expect(partnerRecord?.primary_admin_user_id).toBe(partnerAdmin.id); + } + + const service = module.get(AccountsService); + let _subject = ''; + overrideSendgridService(service, { + sendMail: async ( + context: Context, + to: string[], + cc: string[], + from: string, + subject: string, + text: string, + html: string, + ) => { + _subject = subject; + }, + }); + overrideAdB2cService(service, { + getUser: async (context, externalId) => { + return { + displayName: 'adb2c' + externalId, + id: externalId, + identities: [ + { + signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + issuer: 'xxxxxx', + issuerAssignedId: 'mail@example.com', + }, + ], + }; + }, + getUsers: async (context, externalIds) => + externalIds.map((externalId) => { + return { + displayName: 'adb2c' + externalId, + id: externalId, + identities: [ + { + signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + issuer: 'xxxxxx', + issuerAssignedId: 'mail@example.com', + }, + ], + }; + }), + }); + + // テスト実行 + const context = makeContext(parentAdmin.external_id, 'requestId'); + + await service.updatePartnerInfo( + context, + parentAdmin.external_id, + partner.id, + newPartnerAdmin.id, + partner.company_name, + ); + + { + // DB内が想定通りになっているか確認 + const partnerRecord = await getAccount(source, partner.id); + expect(partnerRecord?.primary_admin_user_id).toBe(newPartnerAdmin.id); + + // パートナーアカウント情報変更完了通知が送信されていること + expect(_subject).toBe('Partner Account Edit Notification [U-124]'); + } + }); + + it('変更対象アカウントが実行者のパートナーアカウントでない場合、エラーなること', async () => { + if (!source) fail(); + const module = await makeTestingModule(source); + if (!module) fail(); + // 新規親アカウントのアカウントを作成する + const { admin: parentAdmin } = await makeTestAccount( + source, + { tier: 3 }, + { external_id: 'parent_external_id' }, + ); + + // 子アカウントを作成する + const { account: partner, admin: partnerAdmin } = await makeTestAccount( + source, + { tier: 4 }, + ); + + // 作成したデータを確認 + { + const partnerRecord = await getAccount(source, partner.id); + expect(partnerRecord?.primary_admin_user_id).toBe(partnerAdmin.id); + } + + const service = module.get(AccountsService); + overrideSendgridService(service, { + sendMail: async ( + context: Context, + to: string[], + cc: string[], + from: string, + subject: string, + text: string, + html: string, + ) => {}, + }); + overrideAdB2cService(service, { + getUser: async (context, externalId) => { + return { + displayName: 'adb2c' + externalId, + id: externalId, + identities: [ + { + signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + issuer: 'xxxxxx', + issuerAssignedId: 'mail@example.com', + }, + ], + }; + }, + getUsers: async (context, externalIds) => + externalIds.map((externalId) => { + return { + displayName: 'adb2c' + externalId, + id: externalId, + identities: [ + { + signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + issuer: 'xxxxxx', + issuerAssignedId: 'mail@example.com', + }, + ], + }; + }), + }); + + // テスト実行 + const context = makeContext(parentAdmin.external_id, 'requestId'); + + try { + await service.updatePartnerInfo( + context, + parentAdmin.external_id, + partner.id, + partnerAdmin.id, + partner.company_name, + ); + fail(); + } catch (e) { + if (e instanceof HttpException) { + expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST); + expect(e.getResponse()).toEqual(makeErrorResponse('E020001')); + } else { + fail(); + } + } + }); + + it('DBアクセスがエラーの場合、エラーなること', async () => { + if (!source) fail(); + const module = await makeTestingModule(source); + if (!module) fail(); + // 新規親アカウントのアカウントを作成する + const { account: parent, admin: parentAdmin } = await makeTestAccount( + source, + { tier: 3 }, + { external_id: 'parent_external_id' }, + ); + + // 子アカウントを作成する + const { account: partner, admin: partnerAdmin } = await makeTestAccount( + source, + { tier: 4, parent_account_id: parent.id }, + ); + + // 作成したデータを確認 + { + const partnerRecord = await getAccount(source, partner.id); + expect(partnerRecord?.primary_admin_user_id).toBe(partnerAdmin.id); + } + + const service = module.get(AccountsService); + overrideSendgridService(service, { + sendMail: async ( + context: Context, + to: string[], + cc: string[], + from: string, + subject: string, + text: string, + html: string, + ) => {}, + }); + overrideAdB2cService(service, { + getUser: async (context, externalId) => { + return { + displayName: 'adb2c' + externalId, + id: externalId, + identities: [ + { + signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + issuer: 'xxxxxx', + issuerAssignedId: 'mail@example.com', + }, + ], + }; + }, + getUsers: async (context, externalIds) => + externalIds.map((externalId) => { + return { + displayName: 'adb2c' + externalId, + id: externalId, + identities: [ + { + signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + issuer: 'xxxxxx', + issuerAssignedId: 'mail@example.com', + }, + ], + }; + }), + }); + + //DBアクセスに失敗するようにする + const accountsRepositoryService = module.get( + AccountsRepositoryService, + ); + accountsRepositoryService.updatePartnerInfo = jest + .fn() + .mockRejectedValue('DB failed'); + + // テスト実行 + const context = makeContext(parentAdmin.external_id, 'requestId'); + + try { + await service.updatePartnerInfo( + context, + parentAdmin.external_id, + partner.id, + partnerAdmin.id, + partner.company_name, + ); + fail(); + } catch (e) { + if (e instanceof HttpException) { + expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR); + expect(e.getResponse()).toEqual(makeErrorResponse('E009999')); + } else { + fail(); + } + } + }); + + it('メール送信に失敗した場合でも、エラーとならず成功となること', async () => { + if (!source) fail(); + const module = await makeTestingModule(source); + if (!module) fail(); + // 新規親アカウントのアカウントを作成する + const { account: parent, admin: parentAdmin } = await makeTestAccount( + source, + { tier: 3 }, + { external_id: 'parent_external_id' }, + ); + + // 子アカウントを作成する + const { account: partner, admin: partnerAdmin } = await makeTestAccount( + source, + { tier: 4, parent_account_id: parent.id, company_name: 'oldCompanyName' }, + ); + + // 作成したデータを確認 + { + const partnerRecord = await getAccount(source, partner.id); + expect(partnerRecord?.company_name).toBe('oldCompanyName'); + } + + const service = module.get(AccountsService); + overrideSendgridService(service, { + sendMail: async ( + context: Context, + to: string[], + cc: string[], + from: string, + subject: string, + text: string, + html: string, + ) => { + throw new Error('sendMail failed'); + }, + }); + overrideAdB2cService(service, { + getUser: async (context, externalId) => { + return { + displayName: 'adb2c' + externalId, + id: externalId, + identities: [ + { + signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + issuer: 'xxxxxx', + issuerAssignedId: 'mail@example.com', + }, + ], + }; + }, + getUsers: async (context, externalIds) => + externalIds.map((externalId) => { + return { + displayName: 'adb2c' + externalId, + id: externalId, + identities: [ + { + signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + issuer: 'xxxxxx', + issuerAssignedId: 'mail@example.com', + }, + ], + }; + }), + }); + + // テスト実行 + const context = makeContext(parentAdmin.external_id, 'requestId'); + + await service.updatePartnerInfo( + context, + parentAdmin.external_id, + partner.id, + partnerAdmin.id, + 'newCompanyName', + ); + + { + // DB内が想定通りになっているか確認 + const partnerRecord = await getAccount(source, partner.id); + expect(partnerRecord?.company_name).toBe('newCompanyName'); + } + }); +}); diff --git a/dictation_server/src/features/accounts/accounts.service.ts b/dictation_server/src/features/accounts/accounts.service.ts index 484dbf0..a69eb2a 100644 --- a/dictation_server/src/features/accounts/accounts.service.ts +++ b/dictation_server/src/features/accounts/accounts.service.ts @@ -3090,4 +3090,124 @@ export class AccountsService { ); } } + /** + * 指定したパートナーアカウントの情報を更新する + * @param context + * @param externalId + * @param targetAccountId // 更新対象のアカウントID + * @param primaryAdminUserId // 更新後のプライマリ管理者のユーザーID + * @param companyName // 更新後の会社名 + * @returns partner account + */ + async updatePartnerInfo( + context: Context, + externalId: string, + targetAccountId: number, + primaryAdminUserId: number, + companyName: string, + ): Promise { + this.logger.log( + `[IN] [${context.getTrackingId()}] ${ + this.updatePartnerInfo.name + } | params: { ` + + `externalId: ${externalId}, ` + + `targetAccountId: ${targetAccountId}, ` + + `primaryAdminUserId: ${primaryAdminUserId}, ` + + `companyName: ${companyName}, };`, + ); + + try { + // 外部IDをもとにユーザー情報を取得する + const { account: parentAccount } = + await this.usersRepository.findUserByExternalId(context, externalId); + + if (parentAccount === null) { + throw new AccountNotFoundError( + `account not found. externalId: ${externalId}`, + ); + } + + // アカウント情報更新処理 + await this.accountRepository.updatePartnerInfo( + context, + parentAccount.id, + targetAccountId, + primaryAdminUserId, + companyName, + ); + + // メール送信処理 + try { + // 実行者のアカウント情報 + const { companyName: parentCompanyName, adminEmails: parentEmails } = + await this.getAccountInformation(context, parentAccount.id); + + // 更新後のパートナーのプライマリ管理者 + const primaryAdmin = await this.usersRepository.findUserById( + context, + primaryAdminUserId, + ); + const adb2cAdmin = await this.adB2cService.getUser( + context, + primaryAdmin.external_id, + ); + const { + displayName: primaryAdminName, + emailAddress: primaryAdminEmail, + } = getUserNameAndMailAddress(adb2cAdmin); + + if (!primaryAdminEmail) { + throw new Error( + `adb2c user mail not found. externalId: ${primaryAdmin.external_id}`, + ); + } + + await this.sendgridService.sendMailWithU124( + context, + companyName, + primaryAdminName, + primaryAdminEmail, + parentCompanyName, + parentEmails, + ); + } catch (e) { + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + // メール送信に関する例外はログだけ出して握りつぶす + } + } catch (e) { + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + if (e instanceof Error) { + switch (e.constructor) { + case AccountNotFoundError: + throw new HttpException( + makeErrorResponse('E010501'), + HttpStatus.BAD_REQUEST, + ); + case AdminUserNotFoundError: + throw new HttpException( + makeErrorResponse('E010502'), + HttpStatus.BAD_REQUEST, + ); + case HierarchyMismatchError: + throw new HttpException( + makeErrorResponse('E020001'), + HttpStatus.BAD_REQUEST, + ); + default: + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } finally { + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${this.updatePartnerInfo.name}`, + ); + } + } } diff --git a/dictation_server/src/gateways/sendgrid/sendgrid.service.ts b/dictation_server/src/gateways/sendgrid/sendgrid.service.ts index 2309c23..f5c219b 100644 --- a/dictation_server/src/gateways/sendgrid/sendgrid.service.ts +++ b/dictation_server/src/gateways/sendgrid/sendgrid.service.ts @@ -89,6 +89,8 @@ export class SendGridService { private readonly templateU122NoParentText: string; private readonly templateU123Html: string; private readonly templateU123Text: string; + private readonly templateU124Html: string; + private readonly templateU124Text: string; constructor(private readonly configService: ConfigService) { this.appDomain = this.configService.getOrThrow('APP_DOMAIN'); @@ -338,6 +340,14 @@ export class SendGridService { path.resolve(__dirname, `../../templates/template_U_123.txt`), 'utf-8', ); + this.templateU124Html = readFileSync( + path.resolve(__dirname, `../../templates/template_U_124.html`), + 'utf-8', + ); + this.templateU124Text = readFileSync( + path.resolve(__dirname, `../../templates/template_U_124.txt`), + 'utf-8', + ); } } @@ -1488,6 +1498,59 @@ export class SendGridService { } } + /** + * U-124のテンプレートを使用したメールを送信する + * @param context + * @param partnerAccountName + * @param partnerPrimaryName + * @param partnerPrimaryMail + * @param dealerAccountName + * @param dealerEmails + * @returns mail with u124 + */ + async sendMailWithU124( + context: Context, + partnerAccountName: string, + partnerPrimaryName: string, + partnerPrimaryMail: string, + dealerAccountName: string, + dealerEmails: string[], + ): Promise { + this.logger.log( + `[IN] [${context.getTrackingId()}] ${this.sendMailWithU124.name}`, + ); + try { + const subject = 'Partner Account Edit Notification [U-124]'; + const url = new URL(this.appDomain).href; + + const html = this.templateU124Html + .replaceAll(CUSTOMER_NAME, partnerAccountName) + .replaceAll(PRIMARY_ADMIN_NAME, partnerPrimaryName) + .replaceAll(DEALER_NAME, dealerAccountName) + .replaceAll(TOP_URL, url); + const text = this.templateU124Text + .replaceAll(CUSTOMER_NAME, partnerAccountName) + .replaceAll(PRIMARY_ADMIN_NAME, partnerPrimaryName) + .replaceAll(DEALER_NAME, dealerAccountName) + .replaceAll(TOP_URL, url); + + // メールを送信する + await this.sendMail( + context, + [partnerPrimaryMail], + dealerEmails, + this.mailFrom, + subject, + text, + html, + ); + } finally { + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${this.sendMailWithU124.name}`, + ); + } + } + /** * メールを送信する * @param context diff --git a/dictation_server/src/repositories/accounts/accounts.repository.service.ts b/dictation_server/src/repositories/accounts/accounts.repository.service.ts index ab49646..b5cbe5d 100644 --- a/dictation_server/src/repositories/accounts/accounts.repository.service.ts +++ b/dictation_server/src/repositories/accounts/accounts.repository.service.ts @@ -35,6 +35,7 @@ import { AccountNotFoundError, AdminUserNotFoundError, DealerAccountNotFoundError, + HierarchyMismatchError, PartnerAccountDeletionError, } from './errors/types'; import { @@ -1768,4 +1769,74 @@ export class AccountsRepositoryService { return users; }); } + /** + * 指定したパートナーアカウントの情報を更新する + * @param context + * @param parentAccountId + * @param targetAccountId + * @param primaryAdminUserId + * @param companyName + * @returns partner info + */ + async updatePartnerInfo( + context: Context, + parentAccountId: number, + targetAccountId: number, + primaryAdminUserId: number, + companyName: string, + ): Promise { + return await this.dataSource.transaction(async (entityManager) => { + const accountRepo = entityManager.getRepository(Account); + const userRepo = entityManager.getRepository(User); + + // 指定したプライマリ管理者が対象アカウント内に存在するかチェック + const primaryAdminUser = await userRepo.findOne({ + where: { + id: primaryAdminUserId, + account_id: targetAccountId, + email_verified: true, + }, + comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, + }); + + if (!primaryAdminUser) { + throw new AdminUserNotFoundError( + `Primary admin user is not found. id: ${primaryAdminUserId}, account_id: ${targetAccountId}`, + ); + } + + // 対象アカウントが存在するかチェック + const targetAccount = await accountRepo.findOne({ + where: { id: targetAccountId }, + comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, + }); + + if (!targetAccount) { + throw new AccountNotFoundError( + `Account is not found. id: ${targetAccountId}`, + ); + } + + // 実行者のアカウントが対象アカウントの親アカウントであるか確認する + if (parentAccountId !== targetAccount.parent_account_id) { + throw new HierarchyMismatchError( + `Target account is not child account. parentAccountId: ${parentAccountId}, targetAccountId: ${targetAccountId}`, + ); + } + + // accountsテーブルレコード更新 + await updateEntity( + accountRepo, + { id: targetAccountId }, + { + company_name: companyName, + primary_admin_user_id: primaryAdminUserId, + }, + this.isCommentOut, + context, + ); + }); + } } diff --git a/dictation_server/src/templates/template_U_124.html b/dictation_server/src/templates/template_U_124.html new file mode 100644 index 0000000..98d7236 --- /dev/null +++ b/dictation_server/src/templates/template_U_124.html @@ -0,0 +1,58 @@ + + + Storage Usage Exceeded Notification [U-119] + + + +
+

<English>

+

Dear $CUSTOMER_NAME$, -> $PRIMARY_ADMIN_NAME$

+

Your account information has been edited by $DEALER_NAME$.

+

+ To check or change your account information, please log in to the ODMS + Cloud.
+ URL: $TOP_URL$ +

+

+ If you 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$, -> $PRIMARY_ADMIN_NAME$

+

Ihre Kontoinformationen wurden von $DEALER_NAME$ bearbeitet.

+

+ Um Ihre Kontoinformationen zu überprüfen oder zu ändern, melden Sie sich + bitte bei der ODMS Cloud an.
+ URL: $TOP_URL$ +

+

+ Wenn Sie diese E-Mail irrtümlich erhalten haben, löschen Sie diese + E-Mail bitte aus Ihrem System.
+ Dies ist eine automatisch generierte E-Mail und diese Mailbox wird nicht + überwacht. Bitte antworten Sie nicht. +

+
+
+

<Français>

+

Chère/Cher $CUSTOMER_NAME$, -> $PRIMARY_ADMIN_NAME$

+

+ Les informations de votre compte ont été modifiées par $DEALER_NAME$. +

+

+ Pour vérifier ou modifier les informations de votre compte, veuillez + vous connecter au ODMS Cloud.
+ URL : $TOP_URL$ +

+

+ 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. +

+
+ + diff --git a/dictation_server/src/templates/template_U_124.txt b/dictation_server/src/templates/template_U_124.txt new file mode 100644 index 0000000..e12dc2c --- /dev/null +++ b/dictation_server/src/templates/template_U_124.txt @@ -0,0 +1,35 @@ + + +Dear $CUSTOMER_NAME$, -> $PRIMARY_ADMIN_NAME$ + +Your account information has been edited by $DEALER_NAME$. + +To check or change your account information, please log in to the ODMS Cloud. +URL: $TOP_URL$ + +If you 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. + + + +Sehr geehrte(r) $CUSTOMER_NAME$, -> $PRIMARY_ADMIN_NAME$ + +Ihre Kontoinformationen wurden von $DEALER_NAME$ bearbeitet. + +Um Ihre Kontoinformationen zu überprüfen oder zu ändern, melden Sie sich bitte bei der ODMS Cloud an. +URL: $TOP_URL$ + +Wenn Sie diese E-Mail irrtümlich erhalten haben, löschen Sie diese E-Mail bitte aus Ihrem System. +Dies ist eine automatisch generierte E-Mail und diese Mailbox wird nicht überwacht. Bitte antworten Sie nicht. + + + +Chère/Cher $CUSTOMER_NAME$, -> $PRIMARY_ADMIN_NAME$ + +Les informations de votre compte ont été modifiées par $DEALER_NAME$. + +Pour vérifier ou modifier les informations de votre compte, veuillez vous connecter au ODMS Cloud. +URL : $TOP_URL$ + +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. \ No newline at end of file