Merge branch 'develop' into release-ccb

This commit is contained in:
makabe 2024-05-07 16:49:42 +09:00
commit c26ad130ed
30 changed files with 1635 additions and 256 deletions

View File

@ -1,5 +1,5 @@
VITE_STAGE=staging
VITE_B2C_CLIENTID=5d8f0db9-4506-41d6-a5bb-5ec39f6eba8d
VITE_B2C_AUTHORITY=https://adb2codmsstg.b2clogin.com/adb2codmsstg.onmicrosoft.com/b2c_1_signin_stg
VITE_B2C_KNOWNAUTHORITIES=adb2codmsstg.b2clogin.com
VITE_B2C_CLIENTID=6ddb8ca0-c39e-4eba-a3c1-d18ea289a315
VITE_B2C_AUTHORITY=https://adb2codmsstaging.b2clogin.com/adb2codmsstaging.onmicrosoft.com/b2c_1_signin_staging
VITE_B2C_KNOWNAUTHORITIES=adb2codmsstaging.b2clogin.com
VITE_DESK_TOP_APP_SCHEME=odms-desktopapp

View File

@ -9,7 +9,8 @@
"clean": "rimraf dist",
"prestart": "npm run clean && npm run build",
"start": "func start",
"test": "sql-migrate up -config=/app/dictation_server/db/dbconfig.yml -env=test && jest -w 1",
"test": "tsc --noEmit && sql-migrate up -config=/app/dictation_server/db/dbconfig.yml -env=test && jest -w 1",
"typecheck": "tsc --noEmit",
"codegen": "sh codegen.sh"
},
"dependencies": {

View File

@ -408,3 +408,13 @@ export const LICENSE_COUNT_ANALYSIS_FRONT_STRING = "LicenseAggregated";
* @const {string}
*/
export const LICENSE_COUNT_ANALYSIS_CONTAINER_NAME = "analysis-licenses";
/**
*
* @const {string}
*/
export const CUSTOMER_NAME = "$CUSTOMER_NAME$";
export const DEALER_NAME = "$DEALER_NAME$";
export const TOP_URL = "$TOP_URL$";
export const USER_NAME = "$USER_NAME$";
export const USER_EMAIL = "$USER_EMAIL$";

View File

@ -18,6 +18,16 @@ import { sign, getJwtKey } from "../common/jwt";
import { AccessToken, SystemAccessToken } from "../common/jwt/types";
import { isImportJson, isStageJson } from "../blobstorage/types/guards";
import https from "https";
import globalAxios from "axios";
// すべてのリクエストのヘッダーにX-Requested-Withを追加
globalAxios.interceptors.request.use((config) => {
// headersがあれば追加、なければ新規作成
config.headers = config.headers || {};
// X-Requested-Withを追加
config.headers["X-Requested-With"] = "XMLHttpRequest";
return config;
});
export async function importUsersProcessing(
context: InvocationContext,

View File

@ -387,7 +387,9 @@ async function sendAlertMail(
// メールを送信
try {
await sendgrid.sendMail(
targetAccount.primaryAdminEmail,
context,
[targetAccount.primaryAdminEmail],
[],
mailFrom,
subject,
text,
@ -447,7 +449,9 @@ async function sendAlertMail(
// メールを送信
try {
await sendgrid.sendMail(
targetAccount.secondaryAdminEmail,
context,
[targetAccount.secondaryAdminEmail],
[],
mailFrom,
subject,
text,
@ -506,7 +510,9 @@ async function sendAlertMail(
// メールを送信
try {
await sendgrid.sendMail(
targetAccount.primaryAdminEmail,
context,
[targetAccount.primaryAdminEmail],
[],
mailFrom,
subject,
text,
@ -564,7 +570,9 @@ async function sendAlertMail(
// メールを送信
try {
await sendgrid.sendMail(
targetAccount.secondaryAdminEmail,
context,
[targetAccount.secondaryAdminEmail],
[],
mailFrom,
subject,
text,

View File

@ -1,14 +1,20 @@
import { app, InvocationContext, Timer } from "@azure/functions";
import { Between, DataSource, In, MoreThan, Repository } from "typeorm";
import { Between, DataSource, In, IsNull, MoreThan, Repository } from "typeorm";
import { User } from "../entity/user.entity";
import { Account } from "../entity/account.entity";
import { License, LicenseAllocationHistory } from "../entity/license.entity";
import * as dotenv from "dotenv";
import {
ADB2C_SIGN_IN_TYPE,
CUSTOMER_NAME,
DEALER_NAME,
LICENSE_ALLOCATED_STATUS,
LICENSE_TYPE,
SWITCH_FROM_TYPE,
TIERS,
TOP_URL,
USER_EMAIL,
USER_NAME,
USER_ROLES,
} from "../constants";
import {
@ -17,10 +23,19 @@ import {
NewAllocatedLicenseExpirationDate,
} from "../common/types/types";
import { initializeDataSource } from "../database/initializeDataSource";
import { readFileSync } from "fs";
import path from "path";
import { SendGridService } from "../sendgrid/sendgrid";
import { AdB2cService } from "../adb2c/adb2c";
import { RedisClient } from "redis";
import { createRedisClient } from "../redis/redis";
export async function licenseAutoAllocationProcessing(
context: InvocationContext,
datasource: DataSource,
redisClient: RedisClient,
sendGrid: SendGridService,
adb2c: AdB2cService,
dateToTrigger?: Date
): Promise<void> {
try {
@ -33,6 +48,7 @@ export async function licenseAutoAllocationProcessing(
currentDateZeroTime = new DateWithZeroTime(dateToTrigger);
currentDateEndTime = new DateWithDayEndTime(dateToTrigger);
}
// 自動更新対象の候補となるアカウントを取得
const accountRepository = datasource.getRepository(Account);
const targetAccounts = await accountRepository.find({
@ -56,6 +72,9 @@ export async function licenseAutoAllocationProcessing(
await allocateLicense(
context,
datasource,
redisClient,
sendGrid,
adb2c,
autoAllocationList,
currentDateZeroTime,
currentDateEndTime
@ -79,7 +98,25 @@ export async function licenseAutoAllocation(
dotenv.config({ path: ".env" });
dotenv.config({ path: ".env.local", override: true });
const datasource = await initializeDataSource(context);
await licenseAutoAllocationProcessing(context, datasource);
let redisClient: RedisClient;
try {
// redis接続
redisClient = createRedisClient();
} catch (e) {
context.log("redis client create failed.");
context.error(e);
throw e;
}
const sendGrid = new SendGridService();
const adb2c = new AdB2cService();
await licenseAutoAllocationProcessing(
context,
datasource,
redisClient,
sendGrid,
adb2c
);
} catch (e) {
context.log("licenseAutoAllocation failed.");
context.error(e);
@ -199,24 +236,45 @@ export async function getAutoAllocatableLicense(
try {
context.log("[IN]getAutoAllocatableLicense");
// 割り当て可能なライセンスを取得
const license = await licenseRepository.findOne({
where: {
account_id: accountId,
status: In([
LICENSE_ALLOCATED_STATUS.REUSABLE,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
]),
expiry_date: MoreThan(currentDateEndTime) || null,
},
order: {
expiry_date: "ASC",
},
const license = await licenseRepository.find({
where: [
{
account_id: accountId,
status: In([
LICENSE_ALLOCATED_STATUS.REUSABLE,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
]),
expiry_date: MoreThan(currentDateEndTime),
},
{
account_id: accountId,
status: In([
LICENSE_ALLOCATED_STATUS.REUSABLE,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
]),
expiry_date: IsNull(),
},
],
});
if (!license) {
if (license.length === 0) {
// 割り当て可能なライセンスが存在しない場合でもエラーとはしたくないので、undifinedを返却する
return undefined;
}
return license;
// ライセンスをソートする
// 有効期限が近いものから割り当てるため、expiry_dateがnullのものは最後にする
const sortedLicense = license.sort((a, b) => {
if (a.expiry_date && b.expiry_date) {
return a.expiry_date.getTime() - b.expiry_date.getTime();
} else if (a.expiry_date && !b.expiry_date) {
return -1;
} else if (!a.expiry_date && b.expiry_date) {
return 1;
} else {
return 0;
}
});
// 有効期限が近いライセンスを返却する
return sortedLicense[0];
} catch (e) {
context.error(e);
context.log("getAutoAllocatableLicense failed.");
@ -237,17 +295,23 @@ export async function getAutoAllocatableLicense(
export async function allocateLicense(
context: InvocationContext,
datasource: DataSource,
redisClient: RedisClient,
sendGrid: SendGridService,
adb2c: AdB2cService,
autoAllocationList: autoAllocationList,
currentDateZeroTime: DateWithZeroTime,
currentDateEndTime: DateWithDayEndTime
): Promise<void> {
context.log("[IN]allocateLicense");
try {
context.log("[IN]allocateLicense");
// 自動更新対象ユーザーにライセンスを割り当てる
// 割り当て可能なライセンスが存在するかどうかのフラグ
let hasAllocatebleLicense = true;
// ユーザーに割り当てられているライセンスが自動更新対象であるかどうかのフラグ
let hasAutoRenewLicense = true;
for (const userId of autoAllocationList.userIds) {
await datasource.transaction(async (entityManager) => {
// フラグの初期化
hasAutoRenewLicense = true;
const licenseRepository = entityManager.getRepository(License);
const licenseAllocationHistoryRepo = entityManager.getRepository(
LicenseAllocationHistory
@ -276,6 +340,7 @@ export async function allocateLicense(
});
if (!allocatedLicense) {
context.log(`skip auto allocation. userID:${userId}`);
hasAutoRenewLicense = false;
return;
}
@ -349,17 +414,276 @@ export async function allocateLicense(
if (!hasAllocatebleLicense) {
break;
}
// ユーザーに割り当てられているライセンスが自動更新対象であるかどうかのフラグがfalseの場合、次のユーザーへ
if (!hasAutoRenewLicense) {
continue;
}
try {
//メール送信に必要な情報をDBから取得
const userRepository = datasource.getRepository(User);
const accountRepository = datasource.getRepository(Account);
// ライセンスを割り当てたユーザーとアカウントの情報を取得
const user = await userRepository.findOne({
where: { id: userId },
});
if (!user) {
throw new Error(`Target user not found. ${userId}`);
}
const account = await accountRepository.findOne({
where: { id: autoAllocationList.accountId },
relations: {
primaryAdminUser: true,
secondaryAdminUser: true,
},
});
if (!account) {
throw new Error(
`Target account not found. ${autoAllocationList.accountId}`
);
}
// アカウントのプライマリー管理者が存在しない場合はエラー
if (!account.primaryAdminUser) {
throw new Error(
`Primary admin user not found. accountID: ${account.id}`
);
}
// 親アカウントが存在する場合は取得
let parentAccount: Account | null = null;
if (account.parent_account_id) {
parentAccount = await accountRepository.findOne({
where: { id: account.parent_account_id },
});
if (!parentAccount) {
throw new Error(
`Parent account not found. accountID: ${account.parent_account_id}`
);
}
}
// アカウントの管理者とライセンスを割り当てたユーザーのメールアドレス取得に必要な外部IDを抽出
const externalIds: string[] = [];
externalIds.push(user.external_id);
externalIds.push(account.primaryAdminUser.external_id);
// セカンダリ管理者が存在する場合はセカンダリ管理者の外部IDも抽出
if (account.secondaryAdminUser) {
externalIds.push(account.secondaryAdminUser.external_id);
}
const adb2cUsers = await getMailAddressAndDisplayNameList(
context,
redisClient,
adb2c,
externalIds
);
// ライセンス割り当てされたユーザーの名前を取得
const userName = adb2cUsers.find(
(adb2cUser) => adb2cUser.externalId === user.external_id
)?.displayName;
if (!userName) {
throw new Error(
`Target ADb2Cuser name not found. externalId=${user.external_id}`
);
}
// ライセンス割り当てされたユーザーのメールアドレスを取得
const userMail = adb2cUsers.find(
(adb2cUser) => adb2cUser.externalId === user.external_id
)?.mailAddress;
if (!userMail) {
throw new Error(
`Target ADb2Cuser mail not found. externalId=${user.external_id}`
);
}
// アカウントのプライマリー管理者のメールアドレスを取得
const adminMails: string[] = [];
const primaryAdminMail = adb2cUsers.find(
(adb2cUser) =>
adb2cUser.externalId === account.primaryAdminUser?.external_id
)?.mailAddress;
if (!primaryAdminMail) {
throw new Error(
`Primary admin user mail not found. externalId=${account.primaryAdminUser.external_id}`
);
}
adminMails.push(primaryAdminMail);
// アカウントのセカンダリ管理者のメールアドレスを取得
const secondaryAdminMail = adb2cUsers.find(
(adb2cUser) =>
adb2cUser.externalId === account.secondaryAdminUser?.external_id
)?.mailAddress;
if (secondaryAdminMail) {
adminMails.push(secondaryAdminMail);
}
// メール送信
await sendMailWithU108(
context,
userName,
userMail,
adminMails,
account.company_name,
parentAccount ? parentAccount.company_name : null,
sendGrid
);
} catch (e) {
context.error(`error=${e}`);
// メール送信に関する例外はログだけ出して握りつぶす
}
}
} catch (e) {
// エラーが発生しても次のアカウントへの処理は継続させるため、例外をthrowせずにreturnだけする
context.error(e);
context.log("allocateLicense failed.");
context.error(e);
return;
} finally {
context.log("[OUT]allocateLicense");
}
}
// adb2cから指定した外部IDのユーザー情報を取得する
export async function getMailAddressAndDisplayNameList(
context: InvocationContext,
redisClient: RedisClient,
adb2c: AdB2cService,
externalIds: string[]
): Promise<
{
externalId: string;
displayName: string;
mailAddress: string;
}[]
> {
context.log("[IN]getUsers");
try {
const users = [] as {
externalId: string;
displayName: string;
mailAddress: string;
}[];
// 外部IDからADB2Cユーザー情報を取得
const adb2cUsers = await adb2c.getUsers(context, redisClient, externalIds);
for (const externalId of externalIds) {
const adb2cUser = adb2cUsers.find((user) => user.id === externalId);
if (!adb2cUser) {
throw new Error(`ADB2C user not found. externalId=${externalId}`);
}
const mailAddress = adb2cUser.identities?.find(
(identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EMAILADDRESS
)?.issuerAssignedId;
if (!mailAddress) {
throw new Error(`ADB2C user mail not found. externalId=${externalId}`);
}
users.push({
externalId: externalId,
displayName: adb2cUser.displayName,
mailAddress: mailAddress,
});
}
return users;
} catch (e) {
context.error(e);
context.log("getUsers failed.");
throw e;
} finally {
context.log("[OUT]getUsers");
}
}
/**
* U-108使
* @param context
* @param userName
* @param userMail
* @param customerAdminMails (primary/secondary)
* @param customerAccountName
* @param dealerAccountName
* @returns mail with u108
*/
export async function sendMailWithU108(
context: InvocationContext,
userName: string,
userMail: string,
customerAdminMails: string[],
customerAccountName: string,
dealerAccountName: string | null,
sendGrid: SendGridService
): Promise<void> {
context.log("[IN] sendMailWithU108");
try {
const subject = "License Assigned Notification [U-108]";
const domain = process.env.APP_DOMAIN;
if (!domain) {
throw new Error("APP_DOMAIN is not defined.");
}
const mailFrom = process.env.MAIL_FROM;
if (!mailFrom) {
throw new Error("MAIL_FROM is not defined.");
}
const url = new URL(domain).href;
let html: string;
let text: string;
if (dealerAccountName === null) {
const templateU108NoParentHtml = readFileSync(
path.resolve(__dirname, `../templates/template_U_108_no_parent.html`),
"utf-8"
);
const templateU108NoParentText = readFileSync(
path.resolve(__dirname, `../templates/template_U_108_no_parent.txt`),
"utf-8"
);
html = templateU108NoParentHtml
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(USER_NAME, userName)
.replaceAll(USER_EMAIL, userMail)
.replaceAll(TOP_URL, url);
text = templateU108NoParentText
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(USER_NAME, userName)
.replaceAll(USER_EMAIL, userMail)
.replaceAll(TOP_URL, url);
} else {
const templateU108Html = readFileSync(
path.resolve(__dirname, `../templates/template_U_108.html`),
"utf-8"
);
const templateU108Text = readFileSync(
path.resolve(__dirname, `../templates/template_U_108.txt`),
"utf-8"
);
html = templateU108Html
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(DEALER_NAME, dealerAccountName)
.replaceAll(USER_NAME, userName)
.replaceAll(USER_EMAIL, userMail)
.replaceAll(TOP_URL, url);
text = templateU108Text
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(DEALER_NAME, dealerAccountName)
.replaceAll(USER_NAME, userName)
.replaceAll(USER_EMAIL, userMail)
.replaceAll(TOP_URL, url);
}
const ccAddress = customerAdminMails.includes(userMail) ? [] : [userMail];
// メールを送信する
await sendGrid.sendMail(
context,
customerAdminMails,
ccAddress,
mailFrom,
subject,
text,
html
);
} finally {
context.log(`[OUT] sendMailWithU108`);
}
}
app.timer("licenseAutoAllocation", {
schedule: "0 0 16 * * *",
handler: licenseAutoAllocation,

View File

@ -9,6 +9,10 @@ import { licenseAutoAllocationProcessing } from "./licenseAutoAllocation";
import * as dotenv from "dotenv";
import { HTTP_METHODS, HTTP_STATUS_CODES } from "../constants";
import { initializeDataSource } from "../database/initializeDataSource";
import { RedisClient } from "redis";
import { createRedisClient } from "../redis/redis";
import { AdB2cService } from "../adb2c/adb2c";
import { SendGridService } from "../sendgrid/sendgrid";
export async function licenseAutoAllocationManualRetry(
req: HttpRequest,
@ -38,14 +42,35 @@ export async function licenseAutoAllocationManualRetry(
dotenv.config({ path: ".env" });
dotenv.config({ path: ".env.local", override: true });
const datasource = await initializeDataSource(context);
await licenseAutoAllocationProcessing(context, datasource, dateToTrigger);
let redisClient: RedisClient;
try {
// redis接続
redisClient = createRedisClient();
} catch (e) {
context.log("redis client create failed.");
context.error(e);
throw e;
}
const sendGrid = new SendGridService();
const adb2c = new AdB2cService();
await licenseAutoAllocationProcessing(
context,
datasource,
redisClient,
sendGrid,
adb2c,
dateToTrigger
);
context.log("Automatic license allocation has been triggered.");
return {
status: HTTP_STATUS_CODES.OK,
body: "Automatic license allocation has been triggered.",
};
} else {
context.log(`Please use the POST method. Requested method = [${req.method}]`);
context.log(
`Please use the POST method. Requested method = [${req.method}]`
);
return {
status: HTTP_STATUS_CODES.BAD_REQUEST,
body: `Please use the POST method. method = [${req.method}]`,

View File

@ -1,3 +1,4 @@
import { InvocationContext } from "@azure/functions";
import sendgrid from "@sendgrid/mail";
import { error } from "console";
@ -9,9 +10,11 @@ export class SendGridService {
sendgrid.setApiKey(process.env.SENDGRID_API_KEY);
}
/**
/**
*
* @param context
* @param to
* @param cc
* @param from
* @param subject
* @param text
@ -19,28 +22,40 @@ export class SendGridService {
* @returns mail
*/
async sendMail(
to: string,
context: InvocationContext,
to: string[],
cc: string[],
from: string,
subject: string,
text: string,
html: string
html: string,
): Promise<void> {
context.log(`[IN] ${this.sendMail.name}`);
try {
const res = await sendgrid
.send({
from: {
email: from,
},
to: {
email: to,
},
to: to.map((v) => ({ email: v })),
cc: cc.map((v) => ({ email: v })),
subject: subject,
text: text,
html: html,
})
.then((v) => v[0]);
context.log(
` status code: ${
res.statusCode
} body: ${JSON.stringify(res.body)}`,
);
} catch (e) {
context.warn(`send mail faild.`);
context.warn(`${this.sendMail.name} error=${e}`);
throw e;
} finally {
context.log(`[OUT] ${this.sendMail.name}`);
}
}
}

View File

@ -0,0 +1,81 @@
<html>
<head>
<title>License Assigned Notification [U-108]</title>
</head>
<body>
<div>
<h3>&lt;English&gt;</h3>
<p>Dear $CUSTOMER_NAME$,</p>
<p>
Please be informed that a license has been assigned to the following
user.<br />
- User Name: $USER_NAME$<br />
- Email: $USER_EMAIL$
</p>
<p>
Please log in to ODMS Cloud to verify the license expiration date.<br />
URL: $TOP_URL$
</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>
Bitte beachten Sie, dass dem folgenden Benutzer eine Lizenz zugewiesen
wurde.<br />
- Nutzername: $USER_NAME$<br />
- Email: $USER_EMAIL$
</p>
<p>
Bitte melden Sie sich bei ODMS Cloud an, um das Ablaufdatum der Lizenz
zu überprüfen.<br />
URL: $TOP_URL$
</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 irrtümlich erhalten haben, löschen Sie diese
E-Mail bitte aus Ihrem System.<br />
Dies ist eine automatisch generierte
E-Mail und diese Mailbox wird nicht überwacht. Bitte antworten Sie
nicht.
</p>
</div>
<div>
<h3>&lt;Français&gt;</h3>
<p>Chère/Cher $CUSTOMER_NAME$,</p>
<p>
Veuillez être informé qu'une licence a été attribuée à l'utilisateur
suivant.<br />
- Nom d'utilisateur: $USER_NAME$<br />
- Email: $USER_EMAIL$
</p>
<p>
Veuillez vous connecter à ODMS Cloud pour vérifier la date d'expiration
de la licence.<br />
URL: $TOP_URL$
</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,47 @@
<English>
Dear $CUSTOMER_NAME$,
Please be informed that a license has been assigned to the following user.
- User Name: $USER_NAME$
- Email: $USER_EMAIL$
Please log in to ODMS Cloud to verify the license expiration date.
URL: $TOP_URL$
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$,
Bitte beachten Sie, dass dem folgenden Benutzer eine Lizenz zugewiesen wurde.
- Nutzername: $USER_NAME$
- Email: $USER_EMAIL$
Bitte melden Sie sich bei ODMS Cloud an, um das Ablaufdatum der Lizenz zu überprüfen.
URL: $TOP_URL$
Wenn Sie Unterstützung bezüglich ODMS Cloud benötigen, wenden Sie sich bitte an $DEALER_NAME$.
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$,
Veuillez être informé qu'une licence a été attribuée à l'utilisateur suivant.
- Nom d'utilisateur: $USER_NAME$
- Email: $USER_EMAIL$
Veuillez vous connecter à ODMS Cloud pour vérifier la date d'expiration de la licence.
URL: $TOP_URL$
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,70 @@
<html>
<head>
<title>License Assigned Notification [U-108]</title>
</head>
<body>
<div>
<h3>&lt;English&gt;</h3>
<p>Dear $CUSTOMER_NAME$,</p>
<p>
Please be informed that a license has been assigned to the following
user.<br />
- User Name: $USER_NAME$<br />
- Email: $USER_EMAIL$
</p>
<p>
Please log in to ODMS Cloud to verify the license expiration date.<br />
URL: $TOP_URL$
</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>
Bitte beachten Sie, dass dem folgenden Benutzer eine Lizenz zugewiesen
wurde.<br />
- Nutzername: $USER_NAME$<br />
- Email: $USER_EMAIL$
</p>
<p>
Bitte melden Sie sich bei ODMS Cloud an, um das Ablaufdatum der Lizenz
zu überprüfen.<br />
URL: $TOP_URL$
</p>
<p>
Wenn Sie diese E-Mail irrtümlich erhalten haben, löschen Sie diese
E-Mail bitte aus Ihrem System.<br />
Dies ist eine automatisch generierte
E-Mail und diese Mailbox wird nicht überwacht. Bitte antworten Sie
nicht.
</p>
</div>
<div>
<h3>&lt;Français&gt;</h3>
<p>Chère/Cher $CUSTOMER_NAME$,</p>
<p>
Veuillez être informé qu'une licence a été attribuée à l'utilisateur
suivant.<br />
- Nom d'utilisateur: $USER_NAME$<br />
- Email: $USER_EMAIL$
</p>
<p>
Veuillez vous connecter à ODMS Cloud pour vérifier la date d'expiration
de la licence.<br />
URL: $TOP_URL$
</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,41 @@
<English>
Dear $CUSTOMER_NAME$,
Please be informed that a license has been assigned to the following user.
- User Name: $USER_NAME$
- Email: $USER_EMAIL$
Please log in to ODMS Cloud to verify the license expiration date.
URL: $TOP_URL$
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$,
Bitte beachten Sie, dass dem folgenden Benutzer eine Lizenz zugewiesen wurde.
- Nutzername: $USER_NAME$
- Email: $USER_EMAIL$
Bitte melden Sie sich bei ODMS Cloud an, um das Ablaufdatum der Lizenz zu überprüfen.
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$,
Veuillez être informé qu'une licence a été attribuée à l'utilisateur suivant.
- Nom d'utilisateur: $USER_NAME$
- Email: $USER_EMAIL$
Veuillez vous connecter à ODMS Cloud pour vérifier la date d'expiration de la licence.
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.

View File

@ -0,0 +1,88 @@
import { InvocationContext } from "@azure/functions";
import { AdB2cUser } from "../../adb2c/types/types";
import { ADB2C_SIGN_IN_TYPE } from "../../constants";
import { RedisClient } from "redis-mock";
// テスト用adb2c
export class AdB2cServiceMock {
/**
* Azure AD B2Cからユーザ情報を取得する
* @param externalIds ID
* @returns
*/
async getUsers(
context: InvocationContext,
redisClient: RedisClient,
externalIds: string[]
): Promise<AdB2cUser[]> {
const AdB2cMockUsers: AdB2cUser[] = [
{
id: "external_id1",
displayName: "test1",
identities: [
{
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: "issuer",
issuerAssignedId: "test1@mail.com",
},
],
},
{
id: "external_id2",
displayName: "test2",
identities: [
{
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: "issuer",
issuerAssignedId: "test2@mail.com",
},
],
},
{
id: "external_id3",
displayName: "test3",
identities: [
{
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: "issuer",
issuerAssignedId: "test3@mail.com",
},
],
},
{
id: "external_id4",
displayName: "test4",
identities: [
{
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: "issuer",
issuerAssignedId: "test4@mail.com",
},
],
},
{
id: "external_id5",
displayName: "test5",
identities: [
{
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: "issuer",
issuerAssignedId: "test5@mail.com",
},
],
},
{
id: "external_id6",
displayName: "test6",
identities: [
{
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: "issuer",
issuerAssignedId: "test6@mail.com",
},
],
},
];
return AdB2cMockUsers;
}
}

View File

@ -0,0 +1,24 @@
import { InvocationContext } from "@azure/functions";
export class SendGridServiceMock {
/**
*
* @param to
* @param from
* @param subject
* @param text
* @param html
* @returns mail
*/
async sendMail(
context: InvocationContext,
to: string[],
cc: string[],
from: string,
subject: string,
text: string,
html: string
): Promise<void> {
return;
}
}

View File

@ -233,6 +233,59 @@ export const createLicense = async (
});
identifiers.pop() as License;
};
export const createAndAllocateLicense = async (
datasource: DataSource,
licenseId: number,
expiry_date: Date | null,
accountId: number,
type: string,
status: string,
allocated_user_id: number | null,
order_id: number | null,
deleted_at: Date | null,
delete_order_id: number | null,
created_at?: Date
): Promise<void> => {
const { identifiers } = await datasource.getRepository(License).insert({
id: licenseId,
expiry_date: expiry_date,
account_id: accountId,
type: type,
status: status,
allocated_user_id: allocated_user_id,
order_id: order_id,
deleted_at: deleted_at,
delete_order_id: delete_order_id,
created_by: "test_runner",
created_at: created_at ? created_at : new Date(),
updated_by: "updater",
updated_at: new Date(),
});
identifiers.pop() as License;
// 割り当てるユーザーがいない場合は履歴を作成しない
if (!allocated_user_id) {
return;
}
// switch_from_typeを作成
// typeが"CARD"の場合は"CARD","TRIAL"の場合は"TRIAL","NORMAL"の場合は"NONE"を設定
const switch_from_type =
type === "CARD" ? "CARD" : type === "TRIAL" ? "TRIAL" : "NONE";
// ライセンスの割り当て履歴を作成
await datasource.getRepository(LicenseAllocationHistory).insert({
license_id: licenseId,
account_id: accountId,
user_id: allocated_user_id ?? -1,
is_allocated: true,
switch_from_type: switch_from_type,
executed_at: new Date(),
created_by: "test_runner",
created_at: new Date(),
updated_by: "updater",
updated_at: new Date(),
});
};
export const createLicenseAllocationHistory = async (
datasource: DataSource,

View File

@ -1,6 +1,6 @@
import { DataSource } from "typeorm";
import { licenseAlertProcessing } from "../functions/licenseAlert";
import { makeTestAccount, createLicense } from "./common/utility";
import { makeTestAccount, createAndAllocateLicense } from "./common/utility";
import * as dotenv from "dotenv";
import {
DateWithDayEndTime,
@ -8,13 +8,13 @@ import {
ExpirationThresholdDate,
NewTrialLicenseExpirationDate,
} from "../common/types/types";
import { AdB2cUser } from "../adb2c/types/types";
import { ADB2C_SIGN_IN_TYPE } from "../constants";
import { SendGridService } from "../sendgrid/sendgrid";
import { AdB2cService } from "../adb2c/adb2c";
import { InvocationContext } from "@azure/functions";
import { RedisClient, createClient } from "redis-mock";
import { createClient } from "redis-mock";
import { promisify } from "util";
import { SendGridServiceMock } from "./common/sendGrid.mock";
import { AdB2cServiceMock } from "./common/adb2c.mock";
describe("licenseAlert", () => {
dotenv.config({ path: ".env" });
@ -56,7 +56,7 @@ describe("licenseAlert", () => {
{ tier: 5 },
{ external_id: "external_id1", accepted_dpa_version: null }
);
await createLicense(
await createAndAllocateLicense(
source,
1,
expiringSoonDate,
@ -103,7 +103,7 @@ describe("licenseAlert", () => {
accepted_dpa_version: null,
}
);
await createLicense(
await createAndAllocateLicense(
source,
1,
expiringSoonDate,
@ -147,7 +147,7 @@ describe("licenseAlert", () => {
{ tier: 5 },
{ external_id: "external_id3", accepted_dpa_version: null }
);
await createLicense(
await createAndAllocateLicense(
source,
1,
expiringSoonDate,
@ -159,7 +159,7 @@ describe("licenseAlert", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
2,
expiryDate,
@ -206,7 +206,7 @@ describe("licenseAlert", () => {
accepted_dpa_version: null,
}
);
await createLicense(
await createAndAllocateLicense(
source,
1,
expiringSoonDate,
@ -254,7 +254,7 @@ describe("licenseAlert", () => {
accepted_privacy_notice_version: null,
}
);
await createLicense(
await createAndAllocateLicense(
source,
1,
expiringSoonDate,
@ -278,97 +278,6 @@ describe("licenseAlert", () => {
});
});
// テスト用sendgrid
export class SendGridServiceMock {
/**
*
* @param to
* @param from
* @param subject
* @param text
* @param html
* @returns mail
*/
async sendMail(
to: string,
from: string,
subject: string,
text: string,
html: string
): Promise<void> {
return;
}
}
// テスト用adb2c
export class AdB2cServiceMock {
/**
* Azure AD B2Cからユーザ情報を取得する
* @param externalIds ID
* @returns
*/
async getUsers(
context: InvocationContext,
redisClient: RedisClient,
externalIds: string[]
): Promise<AdB2cUser[]> {
const AdB2cMockUsers: AdB2cUser[] = [
{
id: "external_id1",
displayName: "test1",
identities: [
{
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: "issuer",
issuerAssignedId: "test1@mail.com",
},
],
},
{
id: "external_id2",
displayName: "test2",
identities: [
{
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: "issuer",
issuerAssignedId: "test2@mail.com",
},
],
},
{
id: "external_id3",
displayName: "test3",
identities: [
{
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: "issuer",
issuerAssignedId: "test3@mail.com",
},
],
},
{
id: "external_id4",
displayName: "test4",
identities: [
{
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: "issuer",
issuerAssignedId: "test4@mail.com",
},
],
},
{
id: "external_id5",
displayName: "test5",
identities: [
{
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: "issuer",
issuerAssignedId: "test5@mail.com",
},
],
},
];
return AdB2cMockUsers;
}
}

View File

@ -8,18 +8,24 @@ import {
import { DateWithDayEndTime } from "../common/types/types";
import {
makeTestAccount,
createLicense,
createAndAllocateLicense,
makeTestUser,
selectLicenseByAllocatedUser,
selectLicenseAllocationHistory,
} from "./common/utility";
import * as dotenv from "dotenv";
import { InvocationContext } from "@azure/functions";
import { AdB2cService } from "../adb2c/adb2c";
import { SendGridService } from "../sendgrid/sendgrid";
import { SendGridServiceMock } from "./common/sendGrid.mock";
import { AdB2cServiceMock } from "./common/adb2c.mock";
import { createClient } from "redis-mock";
describe("licenseAutoAllocation", () => {
dotenv.config({ path: ".env" });
dotenv.config({ path: ".env.test", override: true });
let source: DataSource | null = null;
const redisClient = createClient();
beforeEach(async () => {
source = new DataSource({
type: "sqlite",
@ -40,33 +46,39 @@ describe("licenseAutoAllocation", () => {
it("有効期限が本日のライセンスが自動更新されること", async () => {
if (!source) fail();
const context = new InvocationContext();
const sendgridMock = new SendGridServiceMock() as SendGridService;
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
// 呼び出し回数でテスト成否を判定
const spySend = jest.spyOn(sendgridMock, "sendMail");
const currentDateEndTime = new DateWithDayEndTime();
// アカウント
const account1 = await makeTestAccount(
source,
{ tier: 5 },
{ role: `${USER_ROLES.NONE}` }
{ role: `${USER_ROLES.NONE}`, external_id: "external_id1" }
);
const account2 = await makeTestAccount(
source,
{ tier: 5 },
{ role: `${USER_ROLES.NONE}` }
{ role: `${USER_ROLES.NONE}`, external_id: "external_id6" }
);
// 更新対象のユーザー3role分
const user1 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
external_id: "external_id2",
});
const user2 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.AUTHOR}`,
external_id: "external_id3",
});
const user3 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.TYPIST}`,
external_id: "external_id4",
});
// 更新対象ではないユーザー(まだ有効期限が残っている)
@ -90,10 +102,11 @@ describe("licenseAutoAllocation", () => {
const user7 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
external_id: "external_id5",
});
// 割り当て済みで有効期限が本日のライセンス
await createLicense(
await createAndAllocateLicense(
source,
1,
currentDateEndTime,
@ -105,7 +118,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
2,
currentDateEndTime,
@ -117,7 +130,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
3,
currentDateEndTime,
@ -129,7 +142,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
20,
currentDateEndTime,
@ -141,7 +154,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
5,
currentDateEndTime,
@ -153,7 +166,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
6,
currentDateEndTime,
@ -165,7 +178,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
7,
currentDateEndTime,
@ -183,7 +196,7 @@ describe("licenseAutoAllocation", () => {
nextDate.setDate(nextDate.getDate() + 1);
nextDate.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
nextDate.setMilliseconds(0);
await createLicense(
await createAndAllocateLicense(
source,
4,
nextDate,
@ -204,7 +217,7 @@ describe("licenseAutoAllocation", () => {
date.setDate(date.getDate() + i);
date.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
date.setMilliseconds(0);
await createLicense(
await createAndAllocateLicense(
source,
i + 100,
date,
@ -222,7 +235,7 @@ describe("licenseAutoAllocation", () => {
date.setDate(date.getDate() + 30);
date.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
date.setMilliseconds(0);
await createLicense(
await createAndAllocateLicense(
source,
200,
date,
@ -235,7 +248,13 @@ describe("licenseAutoAllocation", () => {
null
);
await licenseAutoAllocationProcessing(context, source);
await licenseAutoAllocationProcessing(
context,
source,
redisClient,
sendgridMock,
adb2cMock
);
const user1Allocated = await selectLicenseByAllocatedUser(source, user1.id);
const user2Allocated = await selectLicenseByAllocatedUser(source, user2.id);
const user3Allocated = await selectLicenseByAllocatedUser(source, user3.id);
@ -277,11 +296,349 @@ describe("licenseAutoAllocation", () => {
expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
account1.account.id
);
expect(
licenseAllocationHistory.licenseAllocationHistory?.switch_from_type
).toBe("CARD");
// メール送信が行われていることを確認
expect(spySend).toHaveBeenCalledTimes(4);
});
it("新規ライセンスがある場合でも、有効期限が本日のライセンスが自動更新されること", async () => {
if (!source) fail();
const context = new InvocationContext();
const sendgridMock = new SendGridServiceMock() as SendGridService;
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
// 呼び出し回数でテスト成否を判定
const spySend = jest.spyOn(sendgridMock, "sendMail");
const currentDateEndTime = new DateWithDayEndTime();
// アカウント
const account1 = await makeTestAccount(
source,
{ tier: 5 },
{ role: `${USER_ROLES.NONE}`, external_id: "external_id1" }
);
const account2 = await makeTestAccount(
source,
{ tier: 5 },
{ role: `${USER_ROLES.NONE}`, external_id: "external_id6" }
);
// 更新対象のユーザー3role分
const testNoneUser1 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
external_id: "external_id2",
});
const testAuthorUser2 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.AUTHOR}`,
external_id: "external_id3",
});
const testTypistUser3 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.TYPIST}`,
external_id: "external_id4",
});
// 更新対象ではないユーザー(まだ有効期限が残っている)
const testNoneUser4ExpirationRemain = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
});
// 更新対象ではないユーザーauto_renewがfalse
const testNoneUser5AutoRenewFalse = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
auto_renew: false,
});
// 更新対象のユーザーAuthor二人目
const testAuthorUser6 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.AUTHOR}`,
external_id: "external_id5",
});
// 更新対象のユーザー(ただしライセンスが足りない)
const testNoneUser7lackofLicenses = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
});
// 割り当て済みで有効期限が本日のライセンス
await createAndAllocateLicense(
source,
1,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
testNoneUser1.id,
null,
null,
null
);
await createAndAllocateLicense(
source,
2,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
testAuthorUser2.id,
null,
null,
null
);
await createAndAllocateLicense(
source,
3,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.TRIAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
testTypistUser3.id,
null,
null,
null
);
await createAndAllocateLicense(
source,
20,
currentDateEndTime,
account2.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
account2.admin.id,
null,
null,
null
);
await createAndAllocateLicense(
source,
5,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
testNoneUser5AutoRenewFalse.id,
null,
null,
null
);
await createAndAllocateLicense(
source,
6,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
testAuthorUser6.id,
null,
null,
null
);
await createAndAllocateLicense(
source,
7,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
testNoneUser7lackofLicenses.id,
null,
null,
null
);
// 割り当て済みの更新対象ではないライセンス
const nextDate = new Date();
nextDate.setDate(nextDate.getDate() + 1);
nextDate.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
nextDate.setMilliseconds(0);
await createAndAllocateLicense(
source,
4,
nextDate,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
testNoneUser4ExpirationRemain.id,
null,
null,
null
);
// 有効期限が先のライセンスを作成
// idが100のものは有効期限が当日なので自動割り当て対象外
// idが101のものから割り当てられる
for (let i = 0; i < 4; i++) {
const date = new Date();
date.setDate(date.getDate() + i);
date.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
date.setMilliseconds(0);
await createAndAllocateLicense(
source,
i + 100,
date,
account1.account.id,
LICENSE_TYPE.TRIAL,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
null,
null,
null,
null
);
}
// account1用の有効期限が設定されていないライセンスを作成
await createAndAllocateLicense(
source,
99,
null,
account1.account.id,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.REUSABLE,
null,
null,
null,
null
);
// account2用の有効期限が設定されていないライセンスを作成
await createAndAllocateLicense(
source,
200,
null,
account2.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.REUSABLE,
null,
null,
null,
null
);
await licenseAutoAllocationProcessing(
context,
source,
redisClient,
sendgridMock,
adb2cMock
);
const testNoneUser1Allocated = await selectLicenseByAllocatedUser(
source,
testNoneUser1.id
);
const testAuthorUser2Allocated = await selectLicenseByAllocatedUser(
source,
testAuthorUser2.id
);
const testTypistUser3Allocated = await selectLicenseByAllocatedUser(
source,
testTypistUser3.id
);
const testNoneUser4ExpirationRemainAllocated =
await selectLicenseByAllocatedUser(
source,
testNoneUser4ExpirationRemain.id
);
const testNoneUser5AutoRenewFalseAllocated =
await selectLicenseByAllocatedUser(
source,
testNoneUser5AutoRenewFalse.id
);
const testAuthorUser6Allocated = await selectLicenseByAllocatedUser(
source,
testAuthorUser6.id
);
const testNoneUser7lackofLicensesAllocated =
await selectLicenseByAllocatedUser(
source,
testNoneUser7lackofLicenses.id
);
const admin2Allocated = await selectLicenseByAllocatedUser(
source,
account2.admin.id
);
const testNoneUser1LicenseAllocationHistory =
await selectLicenseAllocationHistory(source, testNoneUser1.id, 99);
const testAuthorUser2LicenseAllocationHistory =
await selectLicenseAllocationHistory(source, testAuthorUser2.id, 101);
const testTypistUser3LicenseAllocationHistory =
await selectLicenseAllocationHistory(source, testTypistUser3.id, 103);
// Author、Typist、Noneの優先順位で割り当てられていることを確認
// 複数Authorがいる場合、それぞれに割り当てられていることを確認
expect(testAuthorUser2Allocated.license?.id).toBe(101);
expect(testAuthorUser6Allocated.license?.id).toBe(102);
expect(testTypistUser3Allocated.license?.id).toBe(103);
expect(testNoneUser1Allocated.license?.id).toBe(99);
// 有効期限がまだあるので、ライセンスが更新されていないことを確認
expect(testNoneUser4ExpirationRemainAllocated.license?.id).toBe(4);
// auto_renewがfalseなので、ライセンスが更新されていないことを確認
expect(testNoneUser5AutoRenewFalseAllocated.license?.id).toBe(5);
// ライセンスが足りない場合、ライセンスが更新されていないことを確認
expect(testNoneUser7lackofLicensesAllocated.license?.id).toBe(7);
// 複数アカウント分の処理が正常に行われていることの確認
expect(admin2Allocated.license?.id).toBe(200);
// ライセンス割り当て履歴テーブルが更新されていることを確認
expect(
testNoneUser1LicenseAllocationHistory.licenseAllocationHistory?.user_id
).toBe(testNoneUser1.id);
expect(
testNoneUser1LicenseAllocationHistory.licenseAllocationHistory
?.is_allocated
).toBe(true);
expect(
testNoneUser1LicenseAllocationHistory.licenseAllocationHistory?.account_id
).toBe(account1.account.id);
expect(
testNoneUser1LicenseAllocationHistory.licenseAllocationHistory
?.switch_from_type
).toBe("CARD");
expect(
testAuthorUser2LicenseAllocationHistory.licenseAllocationHistory?.user_id
).toBe(testAuthorUser2.id);
expect(
testAuthorUser2LicenseAllocationHistory.licenseAllocationHistory
?.is_allocated
).toBe(true);
expect(
testAuthorUser2LicenseAllocationHistory.licenseAllocationHistory
?.account_id
).toBe(account1.account.id);
expect(
testAuthorUser2LicenseAllocationHistory.licenseAllocationHistory
?.switch_from_type
).toBe("NONE");
expect(
testTypistUser3LicenseAllocationHistory.licenseAllocationHistory?.user_id
).toBe(testTypistUser3.id);
expect(
testTypistUser3LicenseAllocationHistory.licenseAllocationHistory
?.is_allocated
).toBe(true);
expect(
testTypistUser3LicenseAllocationHistory.licenseAllocationHistory
?.account_id
).toBe(account1.account.id);
expect(
testTypistUser3LicenseAllocationHistory.licenseAllocationHistory
?.switch_from_type
).toBe("TRIAL");
// メール送信が行われていることを確認
expect(spySend).toHaveBeenCalledTimes(5);
});
it("有効期限が指定日のライセンスが自動更新されること(リトライ用)", async () => {
if (!source) fail();
const context = new InvocationContext();
const sendgridMock = new SendGridServiceMock() as SendGridService;
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
// 呼び出し回数でテスト成否を判定
const spySend = jest.spyOn(sendgridMock, "sendMail");
// 2023/11/22の日付を作成
const date1122 = new Date(2023, 10, 22, 23, 59, 59);
const currentDateEndTime = new DateWithDayEndTime(date1122);
@ -289,26 +646,29 @@ describe("licenseAutoAllocation", () => {
const account1 = await makeTestAccount(
source,
{ tier: 5 },
{ role: `${USER_ROLES.NONE}` }
{ role: `${USER_ROLES.NONE}`, external_id: "external_id1" }
);
const account2 = await makeTestAccount(
source,
{ tier: 5 },
{ role: `${USER_ROLES.NONE}` }
{ role: `${USER_ROLES.NONE}`, external_id: "external_id6" }
);
// 更新対象のユーザー3role分
const user1 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
external_id: "external_id2",
});
const user2 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.AUTHOR}`,
external_id: "external_id3",
});
const user3 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.TYPIST}`,
external_id: "external_id4",
});
// 更新対象ではないユーザー(まだ有効期限が残っている)
@ -325,7 +685,7 @@ describe("licenseAutoAllocation", () => {
});
// 割り当て済みで有効期限が11/22のライセンス
await createLicense(
await createAndAllocateLicense(
source,
1,
currentDateEndTime,
@ -337,7 +697,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
2,
currentDateEndTime,
@ -349,7 +709,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
3,
currentDateEndTime,
@ -361,7 +721,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
20,
currentDateEndTime,
@ -373,7 +733,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
5,
currentDateEndTime,
@ -390,7 +750,7 @@ describe("licenseAutoAllocation", () => {
nextDate.setDate(date1122.getDate() + 1);
nextDate.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
nextDate.setMilliseconds(0);
await createLicense(
await createAndAllocateLicense(
source,
4,
nextDate,
@ -409,7 +769,7 @@ describe("licenseAutoAllocation", () => {
// 2023/11/22の日付を作成
const date = new Date(2023, 10, 22, 23, 59, 59);
date.setDate(date.getDate() + i);
await createLicense(
await createAndAllocateLicense(
source,
i + 100,
date,
@ -427,7 +787,7 @@ describe("licenseAutoAllocation", () => {
dateMarch31.setMonth(12);
dateMarch31.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
dateMarch31.setMilliseconds(0);
await createLicense(
await createAndAllocateLicense(
source,
200,
dateMarch31,
@ -439,7 +799,14 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await licenseAutoAllocationProcessing(context, source, date1122);
await licenseAutoAllocationProcessing(
context,
source,
redisClient,
sendgridMock,
adb2cMock,
date1122
);
const user1Allocated = await selectLicenseByAllocatedUser(source, user1.id);
const user2Allocated = await selectLicenseByAllocatedUser(source, user2.id);
const user3Allocated = await selectLicenseByAllocatedUser(source, user3.id);
@ -475,36 +842,50 @@ describe("licenseAutoAllocation", () => {
expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
account1.account.id
);
// メール送信が行われていることを確認
expect(spySend).toHaveBeenCalledTimes(4);
});
it("新たに割り当てられるライセンスが存在しないため、ライセンスが自動更新されない(エラーではない)", async () => {
it("新たに割り当てられるライセンスが存在しないアカウントは、ライセンスが自動更新されない(エラーではない)", async () => {
if (!source) fail();
const context = new InvocationContext();
const sendgridMock = new SendGridServiceMock() as SendGridService;
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
// 呼び出し回数でテスト成否を判定
const spySend = jest.spyOn(sendgridMock, "sendMail");
const currentDateEndTime = new DateWithDayEndTime();
// アカウント
// アカウントと管理者
const account1 = await makeTestAccount(
source,
{ tier: 5 },
{ role: `${USER_ROLES.NONE}` }
{ role: `${USER_ROLES.NONE}`, external_id: "external_id1" }
);
const account2 = await makeTestAccount(
source,
{ tier: 5 },
{ role: `${USER_ROLES.NONE}`, external_id: "external_id5" }
);
// 更新対象のユーザー3role分
// account1の更新対象のユーザー3role分
const user1 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
external_id: "external_id2",
});
const user2 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.AUTHOR}`,
external_id: "external_id3",
});
const user3 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.TYPIST}`,
external_id: "external_id4",
});
// 割り当て済みで有効期限が本日のライセンス
await createLicense(
await createAndAllocateLicense(
source,
1,
currentDateEndTime,
@ -516,7 +897,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
2,
currentDateEndTime,
@ -528,7 +909,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
3,
currentDateEndTime,
@ -540,20 +921,67 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createAndAllocateLicense(
source,
4,
currentDateEndTime,
account2.account.id,
LICENSE_TYPE.TRIAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
account2.admin.id,
null,
null,
null
);
await licenseAutoAllocationProcessing(context, source);
// account2の有効期限が先の未割当ライセンスを作成
const nextDate = new Date();
nextDate.setDate(nextDate.getDate() + 1);
nextDate.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
nextDate.setMilliseconds(0);
await createAndAllocateLicense(
source,
100,
nextDate,
account2.account.id,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
null,
null,
null,
null
);
await licenseAutoAllocationProcessing(
context,
source,
redisClient,
sendgridMock,
adb2cMock
);
const user1Allocated = await selectLicenseByAllocatedUser(source, user1.id);
const user2Allocated = await selectLicenseByAllocatedUser(source, user2.id);
const user3Allocated = await selectLicenseByAllocatedUser(source, user3.id);
const account2AdminAllocated = await selectLicenseByAllocatedUser(
source,
account2.admin.id
);
// ライセンスが更新されていないことを確認
expect(user1Allocated.license?.id).toBe(1);
expect(user2Allocated.license?.id).toBe(2);
expect(user3Allocated.license?.id).toBe(3);
expect(account2AdminAllocated.license?.id).toBe(100);
// メール送信が行われていないことを確認
expect(spySend).toHaveBeenCalledTimes(1);
});
it("tier4のアカウントのため、ライセンスが自動更新されない", async () => {
if (!source) fail();
const context = new InvocationContext();
const sendgridMock = new SendGridServiceMock() as SendGridService;
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
// 呼び出し回数でテスト成否を判定
const spySend = jest.spyOn(sendgridMock, "sendMail");
const currentDateEndTime = new DateWithDayEndTime();
@ -561,25 +989,28 @@ describe("licenseAutoAllocation", () => {
const account1 = await makeTestAccount(
source,
{ tier: 4 },
{ role: `${USER_ROLES.NONE}` }
{ role: `${USER_ROLES.NONE}`, external_id: "external_id1" }
);
// 更新対象のユーザー3role分
const user1 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
external_id: "external_id2",
});
const user2 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.AUTHOR}`,
external_id: "external_id3",
});
const user3 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.TYPIST}`,
external_id: "external_id4",
});
// 割り当て済みで有効期限が本日のライセンス
await createLicense(
await createAndAllocateLicense(
source,
1,
currentDateEndTime,
@ -591,7 +1022,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
2,
currentDateEndTime,
@ -603,7 +1034,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
3,
currentDateEndTime,
@ -624,7 +1055,7 @@ describe("licenseAutoAllocation", () => {
date.setDate(date.getDate() + i);
date.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
date.setMilliseconds(0);
await createLicense(
await createAndAllocateLicense(
source,
i + 100,
date,
@ -638,7 +1069,13 @@ describe("licenseAutoAllocation", () => {
);
}
await licenseAutoAllocationProcessing(context, source);
await licenseAutoAllocationProcessing(
context,
source,
redisClient,
sendgridMock,
adb2cMock
);
const user1Allocated = await selectLicenseByAllocatedUser(source, user1.id);
const user2Allocated = await selectLicenseByAllocatedUser(source, user2.id);
const user3Allocated = await selectLicenseByAllocatedUser(source, user3.id);
@ -646,5 +1083,99 @@ describe("licenseAutoAllocation", () => {
expect(user1Allocated.license?.id).toBe(1);
expect(user2Allocated.license?.id).toBe(2);
expect(user3Allocated.license?.id).toBe(3);
// メール送信が行われていないことを確認
expect(spySend).toHaveBeenCalledTimes(0);
});
it("メール送信に失敗しても、有効期限が本日のライセンスが自動更新されること", async () => {
if (!source) fail();
const context = new InvocationContext();
const sendgridMock = new SendGridServiceMock() as SendGridService;
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
// 呼び出し回数でテスト成否を判定
const spySend = jest.spyOn(sendgridMock, "sendMail");
const currentDateEndTime = new DateWithDayEndTime();
// アカウント
const account1 = await makeTestAccount(
source,
{ tier: 5 },
{ role: `${USER_ROLES.NONE}`, external_id: "external_id1" }
);
// 更新対象のユーザー3role分
const user1 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
external_id: "external_id7", // メール送信失敗用
});
// 割り当て済みで有効期限が本日のライセンス
await createAndAllocateLicense(
source,
1,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
user1.id,
null,
null,
null
);
// 有効期限が先の未割当ライセンスを作成
// idが100のものは有効期限が当日なので自動割り当て対象外
// idが101のものから割り当てられる
for (let i = 0; i < 5; i++) {
const date = new Date();
date.setDate(date.getDate() + i);
date.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
date.setMilliseconds(0);
await createAndAllocateLicense(
source,
i + 100,
date,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
null,
null,
null,
null
);
}
await licenseAutoAllocationProcessing(
context,
source,
redisClient,
sendgridMock,
adb2cMock
);
const user1Allocated = await selectLicenseByAllocatedUser(source, user1.id);
const licenseAllocationHistory = await selectLicenseAllocationHistory(
source,
user1.id,
101
);
// 割り当てられていることを確認
expect(user1Allocated.license?.id).toBe(101);
// ライセンス割り当て履歴テーブルが更新されていることを確認
expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
user1.id
);
expect(
licenseAllocationHistory.licenseAllocationHistory?.is_allocated
).toBe(true);
expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
account1.account.id
);
expect(
licenseAllocationHistory.licenseAllocationHistory?.switch_from_type
).toBe("CARD");
// メール送信が行われていないことを確認
expect(spySend).toHaveBeenCalledTimes(0);
});
});

View File

@ -1,7 +1,7 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"target": "ES2021",
"outDir": "dist",
"rootDir": ".",
"sourceMap": true,

View File

@ -36,4 +36,5 @@ EMAIL_CONFIRM_LIFETIME=86400
REDIS_HOST=redis-cache
REDIS_PORT=6379
REDIS_PASSWORD=omdsredispass
ADB2C_CACHE_TTL=86400
ADB2C_CACHE_TTL=86400
DEALER_ACCOUNT_ID_HIDDEN_LIST=1,2,3,4

View File

@ -37,4 +37,5 @@ REDIS_HOST=redis-cache
REDIS_PORT=6379
REDIS_PASSWORD=omdsredispass
ADB2C_CACHE_TTL=86400
TEMPLATE_ROOT=dist
TEMPLATE_ROOT=dist
DEALER_ACCOUNT_ID_HIDDEN_LIST=50,99

View File

@ -36,6 +36,10 @@ export class EnvValidator {
@IsString()
DB_PASSWORD: string;
@IsOptional()
@IsString()
DEALER_ACCOUNT_ID_HIDDEN_LIST: string;
// .env.local
@IsOptional()
@IsString()

View File

@ -2818,6 +2818,35 @@ describe('getDealers', () => {
],
});
});
it('非表示指定されたDealer以外のDealerを取得できる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 100件のDealerを作成し、country,id,company_nameを取得する
const dealers: { country: string; id: number; name: string }[] = [];
for (let i = 0; i < 100; i++) {
const { id, company_name, country } = (
await makeTestAccount(source, {
parent_account_id: i,
tier: TIERS.TIER4,
country: 'JP',
company_name: `DEALER_${i}`,
})
).account;
dealers.push({ id, name: company_name, country });
}
const service = module.get<AccountsService>(AccountsService);
const context = makeContext(`uuidv4`, 'requestId');
const result = await service.getDealers(context);
// idが50と99のDealerを非表示にする
expect(result.dealers.length).toBe(98);
expect(result).toEqual({
dealers: dealers.filter((dealer) => dealer.id !== 50 && dealer.id !== 99),
});
});
it('0件でもDealerを取得できる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
@ -7454,6 +7483,7 @@ describe('deleteAccountAndData', () => {
expect(expectUserIds).toStrictEqual(userArchiveIds);
});
});
describe('getAccountInfoMinimalAccess', () => {
let source: DataSource | null = null;
beforeAll(async () => {
@ -9890,7 +9920,9 @@ describe('updatePartnerInfo', () => {
subject: string,
text: string,
html: string,
) => {},
) => {
// empty
},
});
overrideAdB2cService(service, {
getUser: async (context, externalId) => {
@ -9977,7 +10009,9 @@ describe('updatePartnerInfo', () => {
subject: string,
text: string,
html: string,
) => {},
) => {
// empty
},
});
overrideAdB2cService(service, {
getUser: async (context, externalId) => {

View File

@ -82,9 +82,13 @@ import {
WorktypeIdNotFoundError,
} from '../../repositories/worktypes/errors/types';
import { getUserNameAndMailAddress } from '../../gateways/adb2c/utils/utils';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class AccountsService {
//プロダクト バックログ項目 4077: [保守]本番環境動作確認用のDealerアカウントを表示しないようにする の対応
private readonly dealerAccountIdHiddenList: number[] = [];
private readonly logger = new Logger(AccountsService.name);
constructor(
private readonly accountRepository: AccountsRepositoryService,
private readonly licensesRepository: LicensesRepositoryService,
@ -94,8 +98,27 @@ export class AccountsService {
private readonly adB2cService: AdB2cService,
private readonly sendgridService: SendGridService,
private readonly blobStorageService: BlobstorageService,
) {}
private readonly logger = new Logger(AccountsService.name);
private readonly configService: ConfigService,
) {
const dealerAccountIdList = this.configService.get<string>(
'DEALER_ACCOUNT_ID_HIDDEN_LIST',
);
// ディーラーアカウントIDリストを数値配列に変換する
// 変換できない場合はエラーをスローする
// 存在しない場合や空文字列の場合は空の配列を返す
if (dealerAccountIdList) {
this.dealerAccountIdHiddenList = dealerAccountIdList
.split(',')
.map((x) => {
const id = parseInt(x, 10);
if (isNaN(id)) {
throw new Error('DEALER_ACCOUNT_ID_HIDDEN_LIST is invalid');
}
return id;
});
}
}
/**
*
* @param accountId
@ -1192,9 +1215,26 @@ export class AccountsService {
const dealerAccounts = await this.accountRepository.findDealerAccounts(
context,
);
// プロダクト バックログ項目 4077: [保守]本番環境動作確認用のDealerアカウントを表示しないようにする の対応
// this.dealerAccountIdHiddenListに含まれるアカウント(動作確認用のアカウント)を除外する。
// 除外したアカウントをlogに出力する
const filteredDealerAccounts = dealerAccounts.filter((dealerAccount) => {
const isHidden = this.dealerAccountIdHiddenList.includes(
dealerAccount.id,
);
if (isHidden) {
this.logger.log(
`[${context.getTrackingId()}] hidden dealer account: ${
dealerAccount.id
}`,
);
}
return !isHidden;
});
// レスポンス用の型に変換
const dealers: GetDealersResponse = {
dealers: dealerAccounts.map((dealerAccount): Dealer => {
dealers: filteredDealerAccounts.map((dealerAccount): Dealer => {
return {
id: dealerAccount.id,
name: dealerAccount.company_name,

View File

@ -63,7 +63,6 @@ import {
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()
@ -195,7 +194,6 @@ export class UsersService {
);
//DBよりアクセス者の所属するアカウントIDを取得する
let adminUser: EntityUser;
let account: Account | null;
try {
adminUser = await this.usersRepository.findUserByExternalId(
context,
@ -210,7 +208,7 @@ export class UsersService {
}
const accountId = adminUser.account_id;
account = adminUser.account;
const account = adminUser.account;
//authorIdが重複していないかチェックする
if (authorId) {

View File

@ -20,10 +20,19 @@ import {
TYPIST_NAME,
VERIFY_LINK,
TEMPORARY_PASSWORD,
EMAIL_DUPLICATION,
AUTHOR_ID_DUPLICATION,
UNEXPECTED_ERROR,
EMAIL_DUPLICATION_EN,
AUTHOR_ID_DUPLICATION_EN,
UNEXPECTED_ERROR_EN,
EMAIL_DUPLICATION_DE,
AUTHOR_ID_DUPLICATION_DE,
UNEXPECTED_ERROR_DE,
EMAIL_DUPLICATION_FR,
AUTHOR_ID_DUPLICATION_FR,
UNEXPECTED_ERROR_FR,
REQUEST_TIME,
NO_ERROR_MESSAGE_EN,
NO_ERROR_MESSAGE_DE,
NO_ERROR_MESSAGE_FR,
} from '../../templates/constants';
import { URL } from 'node:url';
@ -1387,18 +1396,37 @@ export class SendGridService {
`[IN] [${context.getTrackingId()}] ${this.sendMailWithU122.name}`,
);
try {
const duplicateEmailsMsg =
duplicateEmails.length === 0
? 'エラーはありません'
: duplicateEmails.map((x) => `L${x}`).join(', ');
const duplicateAuthorIdsMsg =
duplicateAuthorIds.length === 0
? 'エラーはありません'
: duplicateAuthorIds.map((x) => `L${x}`).join(', ');
const otherErrorsMsg =
otherErrors.length === 0
? 'エラーはありません'
: otherErrors.map((x) => `L${x}`).join(', ');
let duplicateEmailsMsgEn = NO_ERROR_MESSAGE_EN;
let duplicateAuthorIdsMsgEn = NO_ERROR_MESSAGE_EN;
let otherErrorsMsgEn = NO_ERROR_MESSAGE_EN;
let duplicateEmailsMsgDe = NO_ERROR_MESSAGE_DE;
let duplicateAuthorIdsMsgDe = NO_ERROR_MESSAGE_DE;
let otherErrorsMsgDe = NO_ERROR_MESSAGE_DE;
let duplicateEmailsMsgFr = NO_ERROR_MESSAGE_FR;
let duplicateAuthorIdsMsgFr = NO_ERROR_MESSAGE_FR;
let otherErrorsMsgFr = NO_ERROR_MESSAGE_FR;
if (duplicateEmails.length !== 0) {
duplicateEmailsMsgEn = duplicateEmails.map((x) => `L${x}`).join(', ');
duplicateEmailsMsgDe = duplicateEmails.map((x) => `L${x}`).join(', ');
duplicateEmailsMsgFr = duplicateEmails.map((x) => `L${x}`).join(', ');
}
if (duplicateAuthorIds.length !== 0) {
duplicateAuthorIdsMsgEn = duplicateAuthorIds
.map((x) => `L${x}`)
.join(', ');
duplicateAuthorIdsMsgDe = duplicateAuthorIds
.map((x) => `L${x}`)
.join(', ');
duplicateAuthorIdsMsgFr = duplicateAuthorIds
.map((x) => `L${x}`)
.join(', ');
}
if (otherErrors.length !== 0) {
otherErrorsMsgEn = otherErrors.map((x) => `L${x}`).join(', ');
otherErrorsMsgDe = otherErrors.map((x) => `L${x}`).join(', ');
otherErrorsMsgFr = otherErrors.map((x) => `L${x}`).join(', ');
}
const subject = 'User Bulk Registration Failed Notification [U-122]';
@ -1408,27 +1436,51 @@ export class SendGridService {
if (!dealerAccountName) {
html = this.templateU122NoParentHtml
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(EMAIL_DUPLICATION, duplicateEmailsMsg)
.replaceAll(AUTHOR_ID_DUPLICATION, duplicateAuthorIdsMsg)
.replaceAll(UNEXPECTED_ERROR, otherErrorsMsg);
.replaceAll(EMAIL_DUPLICATION_EN, duplicateEmailsMsgEn)
.replaceAll(EMAIL_DUPLICATION_DE, duplicateEmailsMsgDe)
.replaceAll(EMAIL_DUPLICATION_FR, duplicateEmailsMsgFr)
.replaceAll(AUTHOR_ID_DUPLICATION_EN, duplicateAuthorIdsMsgEn)
.replaceAll(AUTHOR_ID_DUPLICATION_DE, duplicateAuthorIdsMsgDe)
.replaceAll(AUTHOR_ID_DUPLICATION_FR, duplicateAuthorIdsMsgFr)
.replaceAll(UNEXPECTED_ERROR_EN, otherErrorsMsgEn)
.replaceAll(UNEXPECTED_ERROR_DE, otherErrorsMsgDe)
.replaceAll(UNEXPECTED_ERROR_FR, otherErrorsMsgFr);
text = this.templateU122NoParentText
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(EMAIL_DUPLICATION, duplicateEmailsMsg)
.replaceAll(AUTHOR_ID_DUPLICATION, duplicateAuthorIdsMsg)
.replaceAll(UNEXPECTED_ERROR, otherErrorsMsg);
.replaceAll(EMAIL_DUPLICATION_EN, duplicateEmailsMsgEn)
.replaceAll(EMAIL_DUPLICATION_DE, duplicateEmailsMsgDe)
.replaceAll(EMAIL_DUPLICATION_FR, duplicateEmailsMsgFr)
.replaceAll(AUTHOR_ID_DUPLICATION_EN, duplicateAuthorIdsMsgEn)
.replaceAll(AUTHOR_ID_DUPLICATION_DE, duplicateAuthorIdsMsgDe)
.replaceAll(AUTHOR_ID_DUPLICATION_FR, duplicateAuthorIdsMsgFr)
.replaceAll(UNEXPECTED_ERROR_EN, otherErrorsMsgEn)
.replaceAll(UNEXPECTED_ERROR_DE, otherErrorsMsgDe)
.replaceAll(UNEXPECTED_ERROR_FR, otherErrorsMsgFr);
} else {
html = this.templateU122Html
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(DEALER_NAME, dealerAccountName)
.replaceAll(EMAIL_DUPLICATION, duplicateEmailsMsg)
.replaceAll(AUTHOR_ID_DUPLICATION, duplicateAuthorIdsMsg)
.replaceAll(UNEXPECTED_ERROR, otherErrorsMsg);
.replaceAll(EMAIL_DUPLICATION_EN, duplicateEmailsMsgEn)
.replaceAll(EMAIL_DUPLICATION_DE, duplicateEmailsMsgDe)
.replaceAll(EMAIL_DUPLICATION_FR, duplicateEmailsMsgFr)
.replaceAll(AUTHOR_ID_DUPLICATION_EN, duplicateAuthorIdsMsgEn)
.replaceAll(AUTHOR_ID_DUPLICATION_DE, duplicateAuthorIdsMsgDe)
.replaceAll(AUTHOR_ID_DUPLICATION_FR, duplicateAuthorIdsMsgFr)
.replaceAll(UNEXPECTED_ERROR_EN, otherErrorsMsgEn)
.replaceAll(UNEXPECTED_ERROR_DE, otherErrorsMsgDe)
.replaceAll(UNEXPECTED_ERROR_FR, otherErrorsMsgFr);
text = this.templateU122Text
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(DEALER_NAME, dealerAccountName)
.replaceAll(EMAIL_DUPLICATION, duplicateEmailsMsg)
.replaceAll(AUTHOR_ID_DUPLICATION, duplicateAuthorIdsMsg)
.replaceAll(UNEXPECTED_ERROR, otherErrorsMsg);
.replaceAll(EMAIL_DUPLICATION_EN, duplicateEmailsMsgEn)
.replaceAll(EMAIL_DUPLICATION_DE, duplicateEmailsMsgDe)
.replaceAll(EMAIL_DUPLICATION_FR, duplicateEmailsMsgFr)
.replaceAll(AUTHOR_ID_DUPLICATION_EN, duplicateAuthorIdsMsgEn)
.replaceAll(AUTHOR_ID_DUPLICATION_DE, duplicateAuthorIdsMsgDe)
.replaceAll(AUTHOR_ID_DUPLICATION_FR, duplicateAuthorIdsMsgFr)
.replaceAll(UNEXPECTED_ERROR_EN, otherErrorsMsgEn)
.replaceAll(UNEXPECTED_ERROR_DE, otherErrorsMsgDe)
.replaceAll(UNEXPECTED_ERROR_FR, otherErrorsMsgFr);
}
// メールを送信する

View File

@ -12,6 +12,18 @@ 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$`;
// 言語ごとに変更
export const EMAIL_DUPLICATION_EN = `$EMAIL_DUPLICATION_EN$`;
export const AUTHOR_ID_DUPLICATION_EN = `$AUTHOR_ID_DUPLICATION_EN$`;
export const UNEXPECTED_ERROR_EN = `$UNEXPECTED_ERROR_EN$`;
export const EMAIL_DUPLICATION_DE = `$EMAIL_DUPLICATION_DE$`;
export const AUTHOR_ID_DUPLICATION_DE = `$AUTHOR_ID_DUPLICATION_DE$`;
export const UNEXPECTED_ERROR_DE = `$UNEXPECTED_ERROR_DE$`;
export const EMAIL_DUPLICATION_FR = `$EMAIL_DUPLICATION_FR$`;
export const AUTHOR_ID_DUPLICATION_FR = `$AUTHOR_ID_DUPLICATION_FR$`;
export const UNEXPECTED_ERROR_FR = `$UNEXPECTED_ERROR_FR$`;
// 言語ごとに当てはまる値
export const NO_ERROR_MESSAGE_EN = 'No errors';
export const NO_ERROR_MESSAGE_DE = 'Keine Fehler';
export const NO_ERROR_MESSAGE_FR = 'Aucune erreur';

View File

@ -18,11 +18,11 @@
1. The e-mail address in the line below has already been registered or is a duplicate of an e-mail address in
another
line.<br />
$EMAIL_DUPLICATION$
  $EMAIL_DUPLICATION_EN$
</p>
<p>
2. The Author ID in the line below is already registered or is a duplicate of an Author ID in another line.<br />
$AUTHOR_ID_DUPLICATION$
  $AUTHOR_ID_DUPLICATION_EN$
</p>
<p>
* E-mail address and Author ID that have already been registered cannot be registered again.<br />
@ -35,7 +35,7 @@
3. An unexpected error occurred during user registration on the following line. If it does not succeed after
trying
again, please contact your dealer.<br />
   $UNEXPECTED_ERROR$
  $UNEXPECTED_ERROR_EN$
</p>
<p>
If you need support regarding the ODMS Cloud, please contact $DEALER_NAME$.
@ -59,12 +59,12 @@
1. Die E-Mail-Adresse in der Zeile unten ist bereits registriert oder ist ein Duplikat einer E-Mail-Adresse in
einer
anderen Zeile.<br />
$EMAIL_DUPLICATION$
  $EMAIL_DUPLICATION_DE$
</p>
<p>
2. Die Author-ID in der Zeile darunter ist bereits registriert oder ein Duplikat einer AuthorID in einer anderen
Zeile.<br />
$AUTHOR_ID_DUPLICATION$
  $AUTHOR_ID_DUPLICATION_DE$
</p>
<p>
* E-Mail-Adresse und Autoren-ID, die bereits registriert wurden, können nicht erneut registriert werden.<br />
@ -78,7 +78,7 @@
3. Bei der Benutzerregistrierung ist in der folgenden Zeile ein unerwarteter Fehler aufgetreten. Sollte es auch
nach
einem erneuten Versuch nicht erfolgreich sein, wenden Sie sich bitte an Ihren Händler.<br />
$UNEXPECTED_ERROR$
  $UNEXPECTED_ERROR_DE$
</p>
<p>
Wenn Sie Unterstützung bezüglich ODMS Cloud benötigen, wenden Sie sich bitte an $DEALER_NAME$.
@ -101,13 +101,13 @@
<p>
1. L'adresse e-mail dans la ligne ci-dessous a déjà été enregistrée ou est un double d'une adresse e-mail dans une
autre ligne.<br />
$EMAIL_DUPLICATION$
  $EMAIL_DUPLICATION_FR$
</p>
<p>
2. L'Identifiant Auteur dans la ligne ci-dessous est déjà enregistré ou est un double d'un Identifiant Auteur dans
une
autre ligne.<br />
$AUTHOR_ID_DUPLICATION$
  $AUTHOR_ID_DUPLICATION_FR$
</p>
<p>
* L'adresse e-mail et l'Identifiant Auteur déjà enregistrés ne peuvent pas être enregistrés à nouveau.<br />
@ -121,7 +121,7 @@
3. Une erreur inattendue s'est produite lors de l'enregistrement de l'utilisateur sur la ligne suivante. Si cela
ne
fonctionne pas après une nouvelle tentative, veuillez contacter votre revendeur.<br />
$UNEXPECTED_ERROR$
  $UNEXPECTED_ERROR_FR$
</p>
<p>
Si vous avez besoin d'assistance concernant ODMS Cloud, veuillez contacter $DEALER_NAME$.

View File

@ -5,16 +5,16 @@ Dear $CUSTOMER_NAME$,
Bulk user registration using the CSV file has failed. The cause and location of the error is shown in 1, 2, and 3 below. ( L = Line )
1. The e-mail address in the line below has already been registered or is a duplicate of an e-mail address in another line.
$EMAIL_DUPLICATION$
$EMAIL_DUPLICATION_EN$
2. The Author ID in the line below is already registered or is a duplicate of an Author ID in another line.
$AUTHOR_ID_DUPLICATION$
$AUTHOR_ID_DUPLICATION_EN$
* E-mail address and Author ID that have already been registered cannot be registered again.
* Rows without errors have been successfully registered. Therefore, if you use the same CSV file and register the user that has been successfully registered, a duplicate error will occur. Please create a CSV file containing only the lines where the error occurred, or manually register them one by one.
3. An unexpected error occurred during user registration on the following line. If it does not succeed after trying again, please contact your dealer.
   $UNEXPECTED_ERROR$
   $UNEXPECTED_ERROR_EN$
If you need support regarding the ODMS Cloud, please contact $DEALER_NAME$.
@ -28,16 +28,16 @@ Sehr geehrte(r) $CUSTOMER_NAME$,
Die Massenregistrierung von Benutzern mithilfe der CSV-Datei ist fehlgeschlagen. Die Ursache und der Ort des Fehlers werden in 1, 2 und 3 unten angezeigt. (L = Linie)
1. Die E-Mail-Adresse in der Zeile unten ist bereits registriert oder ist ein Duplikat einer E-Mail-Adresse in einer anderen Zeile.
$EMAIL_DUPLICATION$
$EMAIL_DUPLICATION_DE$
2. Die Author-ID in der Zeile darunter ist bereits registriert oder ein Duplikat einer AuthorID in einer anderen Zeile.
$AUTHOR_ID_DUPLICATION$
$AUTHOR_ID_DUPLICATION_DE$
* E-Mail-Adresse und Autoren-ID, die bereits registriert wurden, können nicht erneut registriert werden.
* Zeilen ohne Fehler wurden erfolgreich registriert. Wenn Sie daher dieselbe CSV-Datei verwenden und den erfolgreich registrierten Benutzer registrieren, tritt ein doppelter Fehler auf. Bitte erstellen Sie eine CSV-Datei, die nur die Zeilen enthält, in denen der Fehler aufgetreten ist, oder registrieren Sie sie einzeln manuell.
3. Bei der Benutzerregistrierung ist in der folgenden Zeile ein unerwarteter Fehler aufgetreten. Sollte es auch nach einem erneuten Versuch nicht erfolgreich sein, wenden Sie sich bitte an Ihren Händler.
$UNEXPECTED_ERROR$
$UNEXPECTED_ERROR_DE$
Wenn Sie Unterstützung bezüglich ODMS Cloud benötigen, wenden Sie sich bitte an $DEALER_NAME$.
@ -51,16 +51,16 @@ Chère/Cher $CUSTOMER_NAME$,
L'enregistrement groupé des utilisateurs à l'aide du fichier CSV a échoué. La cause et l'emplacement de l'erreur sont indiqués aux points 1, 2 et 3 ci-dessous. ( L = Ligne )
1. L'adresse e-mail dans la ligne ci-dessous a déjà été enregistrée ou est un double d'une adresse e-mail dans une autre ligne.
$EMAIL_DUPLICATION$
$EMAIL_DUPLICATION_FR$
2. L'Identifiant Auteur dans la ligne ci-dessous est déjà enregistré ou est un double d'un Identifiant Auteur dans une autre ligne.
$AUTHOR_ID_DUPLICATION$
$AUTHOR_ID_DUPLICATION_FR$
* L'adresse e-mail et l'Identifiant Auteur déjà enregistrés ne peuvent pas être enregistrés à nouveau.
* Les lignes sans erreurs ont été enregistrées avec succès. Par conséquent, si vous utilisez le même fichier CSV et enregistrez l'utilisateur qui a été enregistré avec succès, une erreur en double se produira. Veuillez créer un fichier CSV contenant uniquement les lignes où l'erreur s'est produite, ou enregistrez-les manuellement une par une.
3. Une erreur inattendue s'est produite lors de l'enregistrement de l'utilisateur sur la ligne suivante. Si cela ne fonctionne pas après une nouvelle tentative, veuillez contacter votre revendeur.
$UNEXPECTED_ERROR$
$UNEXPECTED_ERROR_FR$
Si vous avez besoin d'assistance concernant ODMS Cloud, veuillez contacter $DEALER_NAME$.

View File

@ -18,11 +18,11 @@
1. The e-mail address in the line below has already been registered or is a duplicate of an e-mail address in
another
line.<br />
$EMAIL_DUPLICATION$
  $EMAIL_DUPLICATION_EN$
</p>
<p>
2. The Author ID in the line below is already registered or is a duplicate of an Author ID in another line.<br />
$AUTHOR_ID_DUPLICATION$
  $AUTHOR_ID_DUPLICATION_EN$
</p>
<p>
* E-mail address and Author ID that have already been registered cannot be registered again.<br />
@ -35,7 +35,7 @@
3. An unexpected error occurred during user registration on the following line. If it does not succeed after
trying
again, please contact your dealer.<br />
   $UNEXPECTED_ERROR$
  $UNEXPECTED_ERROR_EN$
</p>
<p>
If you received this e-mail in error, please delete this e-mail from your system.<br />
@ -56,12 +56,12 @@
1. Die E-Mail-Adresse in der Zeile unten ist bereits registriert oder ist ein Duplikat einer E-Mail-Adresse in
einer
anderen Zeile.<br />
$EMAIL_DUPLICATION$
  $EMAIL_DUPLICATION_DE$
</p>
<p>
2. Die Author-ID in der Zeile darunter ist bereits registriert oder ein Duplikat einer AuthorID in einer anderen
Zeile.<br />
$AUTHOR_ID_DUPLICATION$
  $AUTHOR_ID_DUPLICATION_DE$
</p>
<p>
* E-Mail-Adresse und Autoren-ID, die bereits registriert wurden, können nicht erneut registriert werden.<br />
@ -75,7 +75,7 @@
3. Bei der Benutzerregistrierung ist in der folgenden Zeile ein unerwarteter Fehler aufgetreten. Sollte es auch
nach
einem erneuten Versuch nicht erfolgreich sein, wenden Sie sich bitte an Ihren Händler.<br />
$UNEXPECTED_ERROR$
  $UNEXPECTED_ERROR_DE$
</p>
<p>
Wenn Sie diese E-Mail irrtümlich erhalten haben, löschen Sie diese E-Mail bitte aus Ihrem System.<br />
@ -95,13 +95,13 @@
<p>
1. L'adresse e-mail dans la ligne ci-dessous a déjà été enregistrée ou est un double d'une adresse e-mail dans une
autre ligne.<br />
$EMAIL_DUPLICATION$
  $EMAIL_DUPLICATION_FR$
</p>
<p>
2. L'Identifiant Auteur dans la ligne ci-dessous est déjà enregistré ou est un double d'un Identifiant Auteur dans
une
autre ligne.<br />
$AUTHOR_ID_DUPLICATION$
  $AUTHOR_ID_DUPLICATION_FR$
</p>
<p>
* L'adresse e-mail et l'Identifiant Auteur déjà enregistrés ne peuvent pas être enregistrés à nouveau.<br />
@ -115,7 +115,7 @@
3. Une erreur inattendue s'est produite lors de l'enregistrement de l'utilisateur sur la ligne suivante. Si cela
ne
fonctionne pas après une nouvelle tentative, veuillez contacter votre revendeur.<br />
$UNEXPECTED_ERROR$
  $UNEXPECTED_ERROR_FR$
</p>
<p>
Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail de votre système.<br />

View File

@ -5,16 +5,16 @@ Dear $CUSTOMER_NAME$,
Bulk user registration using the CSV file has failed. The cause and location of the error is shown in 1, 2, and 3 below. ( L = Line )
1. The e-mail address in the line below has already been registered or is a duplicate of an e-mail address in another line.
$EMAIL_DUPLICATION$
$EMAIL_DUPLICATION_EN$
2. The Author ID in the line below is already registered or is a duplicate of an Author ID in another line.
$AUTHOR_ID_DUPLICATION$
$AUTHOR_ID_DUPLICATION_EN$
* E-mail address and Author ID that have already been registered cannot be registered again.
* Rows without errors have been successfully registered. Therefore, if you use the same CSV file and register the user that has been successfully registered, a duplicate error will occur. Please create a CSV file containing only the lines where the error occurred, or manually register them one by one.
3. An unexpected error occurred during user registration on the following line. If it does not succeed after trying again, please contact your dealer.
   $UNEXPECTED_ERROR$
   $UNEXPECTED_ERROR_EN$
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.
@ -26,16 +26,16 @@ Sehr geehrte(r) $CUSTOMER_NAME$,
Die Massenregistrierung von Benutzern mithilfe der CSV-Datei ist fehlgeschlagen. Die Ursache und der Ort des Fehlers werden in 1, 2 und 3 unten angezeigt. (L = Linie)
1. Die E-Mail-Adresse in der Zeile unten ist bereits registriert oder ist ein Duplikat einer E-Mail-Adresse in einer anderen Zeile.
$EMAIL_DUPLICATION$
$EMAIL_DUPLICATION_DE$
2. Die Author-ID in der Zeile darunter ist bereits registriert oder ein Duplikat einer AuthorID in einer anderen Zeile.
$AUTHOR_ID_DUPLICATION$
$AUTHOR_ID_DUPLICATION_DE$
* E-Mail-Adresse und Autoren-ID, die bereits registriert wurden, können nicht erneut registriert werden.
* Zeilen ohne Fehler wurden erfolgreich registriert. Wenn Sie daher dieselbe CSV-Datei verwenden und den erfolgreich registrierten Benutzer registrieren, tritt ein doppelter Fehler auf. Bitte erstellen Sie eine CSV-Datei, die nur die Zeilen enthält, in denen der Fehler aufgetreten ist, oder registrieren Sie sie einzeln manuell.
3. Bei der Benutzerregistrierung ist in der folgenden Zeile ein unerwarteter Fehler aufgetreten. Sollte es auch nach einem erneuten Versuch nicht erfolgreich sein, wenden Sie sich bitte an Ihren Händler.
$UNEXPECTED_ERROR$
$UNEXPECTED_ERROR_DE$
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.
@ -47,16 +47,16 @@ Chère/Cher $CUSTOMER_NAME$,
L'enregistrement groupé des utilisateurs à l'aide du fichier CSV a échoué. La cause et l'emplacement de l'erreur sont indiqués aux points 1, 2 et 3 ci-dessous. ( L = Ligne )
1. L'adresse e-mail dans la ligne ci-dessous a déjà été enregistrée ou est un double d'une adresse e-mail dans une autre ligne.
$EMAIL_DUPLICATION$
$EMAIL_DUPLICATION_FR$
2. L'Identifiant Auteur dans la ligne ci-dessous est déjà enregistré ou est un double d'un Identifiant Auteur dans une autre ligne.
$AUTHOR_ID_DUPLICATION$
$AUTHOR_ID_DUPLICATION_FR$
* L'adresse e-mail et l'Identifiant Auteur déjà enregistrés ne peuvent pas être enregistrés à nouveau.
* Les lignes sans erreurs ont été enregistrées avec succès. Par conséquent, si vous utilisez le même fichier CSV et enregistrez l'utilisateur qui a été enregistré avec succès, une erreur en double se produira. Veuillez créer un fichier CSV contenant uniquement les lignes où l'erreur s'est produite, ou enregistrez-les manuellement une par une.
3. Une erreur inattendue s'est produite lors de l'enregistrement de l'utilisateur sur la ligne suivante. Si cela ne fonctionne pas après une nouvelle tentative, veuillez contacter votre revendeur.
$UNEXPECTED_ERROR$
$UNEXPECTED_ERROR_FR$
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.