From b2fef69ea91cfbd7ff99decb194e5aca186accf9 Mon Sep 17 00:00:00 2001 From: "makabe.t" Date: Tue, 19 Dec 2023 02:00:35 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20635:=20=E3=82=A2=E3=82=AB?= =?UTF-8?q?=E3=82=A6=E3=83=B3=E3=83=88=E7=99=BB=E9=8C=B2=E5=AE=8C=E4=BA=86?= =?UTF-8?q?=E9=80=9A=E7=9F=A5=20[U-101]=20=E3=81=AE=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3301: アカウント登録完了通知 [U-101] の実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3301) - アカウント登録完了(認証完了)後にメール送信をする機能を追加しました。 - 合わせてテスト修正をしています。 ## レビューポイント - テンプレートの適用は適切でしょうか。 - テスト修正で対象Sendgridメソッドを上書きしていますが対応として不自然な点はないでしょうか? ## UIの変更 - なし ## 動作確認状況 - ローカルで確認 --- dictation_client/.vscode/settings.json | 4 +- dictation_server/.vscode/settings.json | 2 +- dictation_server/src/common/test/overrides.ts | 19 +++ .../features/users/test/users.service.mock.ts | 3 + .../src/features/users/users.module.ts | 2 + .../src/features/users/users.service.spec.ts | 21 +++ .../src/features/users/users.service.ts | 21 ++- .../src/gateways/sendgrid/sendgrid.service.ts | 52 +++++++ dictation_server/src/templates/constants.ts | 1 + .../src/templates/template_U_101.html | 128 ++++++++++++++++++ .../src/templates/template_U_101.txt | 59 ++++++++ 11 files changed, 308 insertions(+), 4 deletions(-) create mode 100644 dictation_server/src/templates/template_U_101.html create mode 100644 dictation_server/src/templates/template_U_101.txt diff --git a/dictation_client/.vscode/settings.json b/dictation_client/.vscode/settings.json index 2f24e82..4ec4dc1 100644 --- a/dictation_client/.vscode/settings.json +++ b/dictation_client/.vscode/settings.json @@ -27,8 +27,8 @@ "debug.javascript.usePreview": false, "editor.copyWithSyntaxHighlighting": false, "editor.codeActionsOnSave": { - "source.fixAll.eslint": "explicit", - "source.fixAll.stylelint": "explicit" + "source.fixAll.eslint": true, + "source.fixAll.stylelint": true }, "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, diff --git a/dictation_server/.vscode/settings.json b/dictation_server/.vscode/settings.json index 8edcbd3..1f6f864 100644 --- a/dictation_server/.vscode/settings.json +++ b/dictation_server/.vscode/settings.json @@ -1,7 +1,7 @@ { "terminal.integrated.shell.linux": "/bin/bash", "editor.codeActionsOnSave": { - "source.fixAll.eslint": "explicit" + "source.fixAll.eslint": true }, "eslint.format.enable": false, "[javascript]": { diff --git a/dictation_server/src/common/test/overrides.ts b/dictation_server/src/common/test/overrides.ts index bfa8c84..acd0ddc 100644 --- a/dictation_server/src/common/test/overrides.ts +++ b/dictation_server/src/common/test/overrides.ts @@ -92,6 +92,11 @@ export const overrideSendgridService = ( text: string, html: string, ) => Promise; + sendMailWithU101?: ( + context: Context, + customerMail: string, + customerAccountName: string, + ) => Promise; }, ): void => { // テストコードでのみ許される強引な方法でprivateメンバ変数の参照を取得 @@ -113,6 +118,20 @@ export const overrideSendgridService = ( }); } + if (overrides.sendMailWithU101) { + Object.defineProperty(obj, obj.sendMailWithU101.name, { + value: overrides.sendMailWithU101, + writable: true, + }); + } else { + Object.defineProperty(obj, obj.sendMailWithU101.name, { + value: async () => { + return; + }, + writable: true, + }); + } + if (overrides.createMailContentFromEmailConfirm) { Object.defineProperty(obj, obj.createMailContentFromEmailConfirm.name, { value: overrides.createMailContentFromEmailConfirm, 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 908c130..f558ba5 100644 --- a/dictation_server/src/features/users/test/users.service.mock.ts +++ b/dictation_server/src/features/users/test/users.service.mock.ts @@ -18,6 +18,7 @@ import { } from '../../../common/types/sort'; import { AdB2cUser } from '../../../gateways/adb2c/types/types'; import { ADB2C_SIGN_IN_TYPE } from '../../../constants'; +import { AccountsRepositoryService } from '../../../repositories/accounts/accounts.repository.service'; export type SortCriteriaRepositoryMockValue = { updateSortCriteria: SortCriteria | Error; @@ -80,6 +81,8 @@ export const makeUsersServiceMock = async ( }) .useMocker((token) => { switch (token) { + case AccountsRepositoryService: + return {}; case UsersRepositoryService: return makeUsersRepositoryMock(usersRepositoryMockValue); case LicensesRepositoryService: diff --git a/dictation_server/src/features/users/users.module.ts b/dictation_server/src/features/users/users.module.ts index f349a95..b5f5963 100644 --- a/dictation_server/src/features/users/users.module.ts +++ b/dictation_server/src/features/users/users.module.ts @@ -8,9 +8,11 @@ import { LicensesRepositoryModule } from '../../repositories/licenses/licenses.r import { UsersController } from './users.controller'; import { UsersService } from './users.service'; import { AuthService } from '../auth/auth.service'; +import { AccountsRepositoryModule } from '../../repositories/accounts/accounts.repository.module'; @Module({ imports: [ + AccountsRepositoryModule, UsersRepositoryModule, LicensesRepositoryModule, SortCriteriaRepositoryModule, diff --git a/dictation_server/src/features/users/users.service.spec.ts b/dictation_server/src/features/users/users.service.spec.ts index 1200def..d120732 100644 --- a/dictation_server/src/features/users/users.service.spec.ts +++ b/dictation_server/src/features/users/users.service.spec.ts @@ -94,6 +94,12 @@ describe('UsersService.confirmUser', () => { }); const service = module.get(UsersService); + overrideSendgridService(service, { + sendMailWithU101: async () => { + return; + }, + }); + // account id:1, user id: 2のトークン const token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw'; @@ -141,6 +147,11 @@ describe('UsersService.confirmUser', () => { if (!module) fail(); const token = 'invalid.id.token'; const service = module.get(UsersService); + overrideSendgridService(service, { + sendMailWithU101: async () => { + return; + }, + }); const context = makeContext(`uuidv4`, 'requestId'); await expect(service.confirmUser(context, token)).rejects.toEqual( new HttpException(makeErrorResponse('E000101'), HttpStatus.BAD_REQUEST), @@ -175,6 +186,11 @@ describe('UsersService.confirmUser', () => { email_verified: false, }); const service = module.get(UsersService); + overrideSendgridService(service, { + sendMailWithU101: async () => { + return; + }, + }); const token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw'; const context = makeContext(`uuidv4`, 'requestId'); @@ -187,6 +203,11 @@ describe('UsersService.confirmUser', () => { const module = await makeTestingModule(source); if (!module) fail(); const service = module.get(UsersService); + overrideSendgridService(service, { + sendMailWithU101: async () => { + return; + }, + }); const token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw'; const context = makeContext(`uuidv4`, 'requestId'); diff --git a/dictation_server/src/features/users/users.service.ts b/dictation_server/src/features/users/users.service.ts index cb7606b..16e1339 100644 --- a/dictation_server/src/features/users/users.service.ts +++ b/dictation_server/src/features/users/users.service.ts @@ -33,7 +33,6 @@ import { UserNotFoundError, } from '../../repositories/users/errors/types'; import { - ADB2C_SIGN_IN_TYPE, LICENSE_EXPIRATION_THRESHOLD_DAYS, MANUAL_RECOVERY_REQUIRED, OPTION_ITEM_VALUE_TYPE_NUMBER, @@ -51,6 +50,7 @@ import { } from '../../repositories/licenses/errors/types'; import { AccountNotFoundError } from '../../repositories/accounts/errors/types'; import { getUserNameAndMailAddress } from '../../gateways/adb2c/utils/utils'; +import { AccountsRepositoryService } from '../../repositories/accounts/accounts.repository.service'; @Injectable() export class UsersService { @@ -58,6 +58,7 @@ export class UsersService { private readonly mailFrom: string; private readonly appDomain: string; constructor( + private readonly accountsRepository: AccountsRepositoryService, private readonly usersRepository: UsersRepositoryService, private readonly licensesRepository: LicensesRepositoryService, private readonly sortCriteriaRepository: SortCriteriaRepositoryService, @@ -98,6 +99,24 @@ export class UsersService { context, userId, ); + + try { + const { company_name: companyName } = + await this.accountsRepository.findAccountById( + context, + decodedToken.accountId, + ); + + // アカウント認証が完了した旨をメール送信する + await this.sendgridService.sendMailWithU101( + context, + decodedToken.email, + companyName, + ); + } catch (e) { + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + // メール送信に関する例外はログだけ出して握りつぶす + } } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); if (e instanceof Error) { diff --git a/dictation_server/src/gateways/sendgrid/sendgrid.service.ts b/dictation_server/src/gateways/sendgrid/sendgrid.service.ts index 5482611..e20fd2b 100644 --- a/dictation_server/src/gateways/sendgrid/sendgrid.service.ts +++ b/dictation_server/src/gateways/sendgrid/sendgrid.service.ts @@ -11,6 +11,7 @@ import { DEALER_NAME, LICENSE_QUANTITY, PO_NUMBER, + TOP_URL, } from '../../templates/constants'; @Injectable() @@ -21,6 +22,8 @@ export class SendGridService { private readonly mailFrom: string; private readonly templateEmailVerifyHtml: string; private readonly templateEmailVerifyText: string; + private readonly templateU101Html: string; + private readonly templateU101Text: string; private readonly templateU105Html: string; private readonly templateU105Text: string; private readonly templateU106Html: string; @@ -48,6 +51,15 @@ export class SendGridService { 'utf-8', ); + this.templateU101Html = readFileSync( + path.resolve(__dirname, `../../templates/template_U_101.html`), + 'utf-8', + ); + this.templateU101Text = readFileSync( + path.resolve(__dirname, `../../templates/template_U_101.txt`), + 'utf-8', + ); + this.templateU105Html = readFileSync( path.resolve(__dirname, `../../templates/template_U_105.html`), 'utf-8', @@ -178,6 +190,46 @@ export class SendGridService { }; } + /** + * U-101のテンプレートを使用したメールを送信する + * @param context + * @param customerMail アカウント登録を行った管理者ユーザーのメールアドレス + * @param customerAccountName アカウント登録した会社名 + * @returns mail with u101 + */ + async sendMailWithU101( + context: Context, + customerMail: string, + customerAccountName: string, + ): Promise { + this.logger.log( + `[IN] [${context.getTrackingId()}] ${this.sendMailWithU101.name}`, + ); + try { + const subject = 'Account Registered Notification [U-101]'; + const html = this.templateU101Html + .replaceAll(CUSTOMER_NAME, customerAccountName) + .replaceAll(TOP_URL, this.appDomain); + const text = this.templateU101Text + .replaceAll(CUSTOMER_NAME, customerAccountName) + .replaceAll(TOP_URL, this.appDomain); + + await this.sendMail( + context, + [customerMail], + [], + this.mailFrom, + subject, + text, + html, + ); + } finally { + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${this.sendMailWithU101.name}`, + ); + } + } + /** * U-105のテンプレートを使用したメールを送信する * @param context diff --git a/dictation_server/src/templates/constants.ts b/dictation_server/src/templates/constants.ts index 920b4f1..b715abb 100644 --- a/dictation_server/src/templates/constants.ts +++ b/dictation_server/src/templates/constants.ts @@ -2,3 +2,4 @@ export const CUSTOMER_NAME = '$CUSTOMER_NAME$'; export const DEALER_NAME = '$DEALER_NAME$'; export const LICENSE_QUANTITY = '$LICENSE_QUANTITY$'; export const PO_NUMBER = '$PO_NUMBER$'; +export const TOP_URL = '$TOP_URL$'; diff --git a/dictation_server/src/templates/template_U_101.html b/dictation_server/src/templates/template_U_101.html new file mode 100644 index 0000000..8b822de --- /dev/null +++ b/dictation_server/src/templates/template_U_101.html @@ -0,0 +1,128 @@ + + + Account Registered Notification [U-101] + + +
+

<English>

+

Dear $CUSTOMER_NAME$,

+

+ Thank you for choosing ODMS Cloud. Your account has been successfully + registered. +

+

+ We have granted [100] trial licenses to your account which is valid for + 30 days. During the trial, you can try all the features of ODMS Cloud. +

+

+ If you wish to continue using ODMS Cloud after the trial period has + expired, please contact an authorized OM SYSTEM audio dealer to purchase + annual licenses. Various settings including dealer selection can be + configured within ODMS Cloud under the Account tab. +

+

+ Please log in to ODMS Cloud to configure your user setting and and + verify the license expiration date.
+ URL: $TOP_URL$ +

+

+ After you have selected a dealer, to request the number of licenses + please select Subscription tab. Licenses issued by dealers will be + stored in your license Inventory. +

+

+ If you need assistance with ODMS Cloud, please contact your selected + approved OM SYSTEM audio dealer directly. +

+ +

+ 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$,

+

+ Vielen Dank, dass Sie sich für ODMS Cloud entschieden haben. Ihr Konto + wurde erfolgreich registriert. +

+

+ Wir haben Ihrem Konto [100] Testlizenzen gewährt, die 30 Tage gültig + sind. Während der Testversion können Sie alle Funktionen von ODMS Cloud + ausprobieren. +

+

+ Wenn Sie ODMS Cloud nach Ablauf des Testzeitraums weiterhin nutzen + möchten, wenden Sie sich bitte an einen autorisierten OM + SYSTEM-Audiohändler, um Jahreslizenzen zu erwerben. Verschiedene + Einstellungen, einschließlich der Händlerauswahl, können in der ODMS + Cloud auf der Registerkarte „Konto“ konfiguriert werden. +

+

+ Bitte melden Sie sich bei ODMS Cloud an, um Ihre Benutzereinstellungen + zu konfigurieren und das Ablaufdatum der Lizenz zu überprüfen.
+ URL: $TOP_URL$ +

+

+ Nachdem Sie einen Händler ausgewählt haben, wählen Sie bitte die + Registerkarte „Abonnement“ aus, um die Anzahl der Lizenzen anzufordern. + Von Händlern ausgestellte Lizenzen werden in Ihrem Lizenzbestand + gespeichert. +

+

+ Wenn Sie Hilfe mit ODMS Cloud benötigen, wenden Sie sich bitte direkt an + Ihren ausgewählten zugelassenen OM SYSTEM-Audiohändler. +

+

+ 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$,

+

+ Merci d'avoir choisi ODMS Cloud. Votre compte a été enregistré avec + succès. +

+

+ Nous avons accordé [100] licences d'essai à votre compte, valables 30 + jours. Pendant la période d'essai, vous pouvez essayer toutes les + fonctionnalités d'ODMS Cloud. +

+

+ Si vous souhaitez continuer à utiliser ODMS Cloud après l'expiration de + la période d'essai, veuillez contacter un concessionnaire audio agréé OM + SYSTEM pour acheter des licences annuelles. Divers paramètres, y compris + la sélection du concessionnaire, peuvent être configurés dans ODMS Cloud + sous l'onglet Compte. +

+

+ Veuillez vous connecter à ODMS Cloud pour configurer vos paramètres + utilisateur et vérifier la date d'expiration de la licence.
+ URL: $TOP_URL$ +

+

+ Après avoir sélectionné un concessionnaire, pour demander le nombre de + licences, veuillez sélectionner l'onglet Abonnement. Les licences + délivrées par les concessionnaires seront stockées dans votre inventaire + de licences. +

+

+ Si vous avez besoin d'aide avec ODMS Cloud, veuillez contacter + directement votre concessionnaire audio OM SYSTEM agréé sélectionné. +

+

+ 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_101.txt b/dictation_server/src/templates/template_U_101.txt new file mode 100644 index 0000000..bb152a9 --- /dev/null +++ b/dictation_server/src/templates/template_U_101.txt @@ -0,0 +1,59 @@ + + +Dear $CUSTOMER_NAME$, + +Thank you for choosing ODMS Cloud. Your account has been successfully registered. + +We have granted [100] trial licenses to your account which is valid for 30 days. During the trial, you can try all the features of ODMS Cloud. + +If you wish to continue using ODMS Cloud after the trial period has expired, please contact an authorized OM SYSTEM audio dealer to purchase annual licenses. Various settings including dealer selection can be configured within ODMS Cloud under the Account tab. + +Please log in to ODMS Cloud to configure your user setting and and verify the license expiration date. +URL: $TOP_URL$ + +After you have selected a dealer, to request the number of licenses please select Subscription tab. Licenses issued by dealers will be stored in your license Inventory. + +If you need assistance with ODMS Cloud, please contact your selected approved OM SYSTEM audio dealer directly. + +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. + + + +Sehr geehrte(r) $CUSTOMER_NAME$, + +Vielen Dank, dass Sie sich für ODMS Cloud entschieden haben. Ihr Konto wurde erfolgreich registriert. + +Wir haben Ihrem Konto [100] Testlizenzen gewährt, die 30 Tage gültig sind. Während der Testversion können Sie alle Funktionen von ODMS Cloud ausprobieren. + +Wenn Sie ODMS Cloud nach Ablauf des Testzeitraums weiterhin nutzen möchten, wenden Sie sich bitte an einen autorisierten OM SYSTEM-Audiohändler, um Jahreslizenzen zu erwerben. Verschiedene Einstellungen, einschließlich der Händlerauswahl, können in der ODMS Cloud auf der Registerkarte „Konto“ konfiguriert werden. + +Bitte melden Sie sich bei ODMS Cloud an, um Ihre Benutzereinstellungen zu konfigurieren und das Ablaufdatum der Lizenz zu überprüfen. +URL: $TOP_URL$ + +Nachdem Sie einen Händler ausgewählt haben, wählen Sie bitte die Registerkarte „Abonnement“ aus, um die Anzahl der Lizenzen anzufordern. Von Händlern ausgestellte Lizenzen werden in Ihrem Lizenzbestand gespeichert. + +Wenn Sie Hilfe mit ODMS Cloud benötigen, wenden Sie sich bitte direkt an Ihren ausgewählten zugelassenen OM SYSTEM-Audiohändler. + +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. + + + +Chère/Cher $CUSTOMER_NAME$, + +Merci d'avoir choisi ODMS Cloud. Votre compte a été enregistré avec succès. + +Nous avons accordé [100] licences d'essai à votre compte, valables 30 jours. Pendant la période d'essai, vous pouvez essayer toutes les fonctionnalités d'ODMS Cloud. + +Si vous souhaitez continuer à utiliser ODMS Cloud après l'expiration de la période d'essai, veuillez contacter un concessionnaire audio agréé OM SYSTEM pour acheter des licences annuelles. Divers paramètres, y compris la sélection du concessionnaire, peuvent être configurés dans ODMS Cloud sous l'onglet Compte. + +Veuillez vous connecter à ODMS Cloud pour configurer vos paramètres utilisateur et vérifier la date d'expiration de la licence. +URL: $TOP_URL$ + +Après avoir sélectionné un concessionnaire, pour demander le nombre de licences, veuillez sélectionner l'onglet Abonnement. Les licences délivrées par les concessionnaires seront stockées dans votre inventaire de licences. + +Si vous avez besoin d'aide avec ODMS Cloud, veuillez contacter directement votre concessionnaire audio OM SYSTEM agréé sélectionné. + +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