Merged PR 644: Dictation Workflow完了通知 [U-117] の実装
## 概要 [Task3313: Dictation Workflow完了通知 [U-117] の実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3313) - 文字起こし完了時にメール送信する機能を実装しました。 - npm run formatで変更あった箇所も入っています。 ## レビューポイント - SendGridServiceのIFを「こうしたほうがいいかも」とかあれば。 - メール送信に必要な内容取得で効率的にできそうな部分ないか? ## UIの変更 - なし ## 動作確認状況 - ローカルでnpm run testが通ることを確認 - ローカルでメール送信されることを確認
This commit is contained in:
parent
a6f56d71ee
commit
9baae2d2dc
@ -6,14 +6,18 @@ import { TasksRepositoryModule } from '../../repositories/tasks/tasks.repository
|
||||
import { AdB2cModule } from '../../gateways/adb2c/adb2c.module';
|
||||
import { UserGroupsRepositoryModule } from '../../repositories/user_groups/user_groups.repository.module';
|
||||
import { NotificationhubModule } from '../../gateways/notificationhub/notificationhub.module';
|
||||
import { SendGridModule } from '../../gateways/sendgrid/sendgrid.module';
|
||||
import { AccountsRepositoryModule } from '../../repositories/accounts/accounts.repository.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
AccountsRepositoryModule,
|
||||
UsersRepositoryModule,
|
||||
UserGroupsRepositoryModule,
|
||||
TasksRepositoryModule,
|
||||
AdB2cModule,
|
||||
NotificationhubModule,
|
||||
SendGridModule,
|
||||
],
|
||||
providers: [TasksService],
|
||||
controllers: [TasksController],
|
||||
|
||||
@ -33,15 +33,20 @@ import { NotificationhubService } from '../../gateways/notificationhub/notificat
|
||||
import { UserGroupsRepositoryService } from '../../repositories/user_groups/user_groups.repository.service';
|
||||
import { Context } from '../../common/log';
|
||||
import { User } from '../../repositories/users/entity/user.entity';
|
||||
import { SendGridService } from '../../gateways/sendgrid/sendgrid.service';
|
||||
import { getUserNameAndMailAddress } from '../../gateways/adb2c/utils/utils';
|
||||
import { AccountsRepositoryService } from '../../repositories/accounts/accounts.repository.service';
|
||||
|
||||
@Injectable()
|
||||
export class TasksService {
|
||||
private readonly logger = new Logger(TasksService.name);
|
||||
constructor(
|
||||
private readonly accountsRepository: AccountsRepositoryService,
|
||||
private readonly taskRepository: TasksRepositoryService,
|
||||
private readonly usersRepository: UsersRepositoryService,
|
||||
private readonly userGroupsRepositoryService: UserGroupsRepositoryService,
|
||||
private readonly adB2cService: AdB2cService,
|
||||
private readonly sendgridService: SendGridService,
|
||||
private readonly notificationhubService: NotificationhubService,
|
||||
) {}
|
||||
|
||||
@ -357,17 +362,103 @@ export class TasksService {
|
||||
this.checkin.name
|
||||
} | params: { audioFileId: ${audioFileId}, externalId: ${externalId} };`,
|
||||
);
|
||||
const { id } = await this.usersRepository.findUserByExternalId(
|
||||
const user = await this.usersRepository.findUserByExternalId(
|
||||
context,
|
||||
externalId,
|
||||
);
|
||||
|
||||
return await this.taskRepository.checkin(
|
||||
await this.taskRepository.checkin(
|
||||
context,
|
||||
audioFileId,
|
||||
id,
|
||||
user.id,
|
||||
TASK_STATUS.IN_PROGRESS,
|
||||
);
|
||||
|
||||
// メール送信処理
|
||||
try {
|
||||
// タスク情報の取得
|
||||
const task = await this.taskRepository.getTaskAndAudioFile(
|
||||
context,
|
||||
audioFileId,
|
||||
user.account_id,
|
||||
[TASK_STATUS.FINISHED],
|
||||
);
|
||||
if (!task) {
|
||||
throw new Error(
|
||||
`task not found. audioFileId: ${audioFileId}. account_id: ${user.account_id}`,
|
||||
);
|
||||
}
|
||||
|
||||
// author情報の取得
|
||||
if (!task.file?.author_id) {
|
||||
throw new Error(
|
||||
`author_id not found. audioFileId: ${audioFileId}. account_id: ${user.account_id}`,
|
||||
);
|
||||
}
|
||||
const { external_id: authorExternalId } =
|
||||
await this.usersRepository.findUserByAuthorId(
|
||||
context,
|
||||
task.file.author_id,
|
||||
user.account_id,
|
||||
);
|
||||
|
||||
// プライマリ管理者を取得
|
||||
const { external_id: primaryAdminExternalId } =
|
||||
await this.getPrimaryAdminUser(context, user.account_id);
|
||||
|
||||
// ADB2C情報を取得する
|
||||
const usersInfo = await this.adB2cService.getUsers(context, [
|
||||
externalId,
|
||||
authorExternalId,
|
||||
primaryAdminExternalId,
|
||||
]);
|
||||
|
||||
// メール送信に必要な情報を取得
|
||||
const author = usersInfo.find((x) => x.id === authorExternalId);
|
||||
if (!author) {
|
||||
throw new Error(`author not found. id=${authorExternalId}`);
|
||||
}
|
||||
const { displayName: authorName, emailAddress: authorEmail } =
|
||||
getUserNameAndMailAddress(author);
|
||||
if (!authorEmail) {
|
||||
throw new Error(`author email not found. id=${authorExternalId}`);
|
||||
}
|
||||
|
||||
const typist = usersInfo.find((x) => x.id === externalId);
|
||||
if (!typist) {
|
||||
throw new Error(`typist not found. id=${externalId}`);
|
||||
}
|
||||
const { displayName: typistName, emailAddress: typistEmail } =
|
||||
getUserNameAndMailAddress(typist);
|
||||
if (!typistEmail) {
|
||||
throw new Error(`typist email not found. id=${externalId}`);
|
||||
}
|
||||
|
||||
const primaryAdmin = usersInfo.find(
|
||||
(x) => x.id === primaryAdminExternalId,
|
||||
);
|
||||
if (!primaryAdmin) {
|
||||
throw new Error(
|
||||
`primary admin not found. id=${primaryAdminExternalId}`,
|
||||
);
|
||||
}
|
||||
const { displayName: primaryAdminName } =
|
||||
getUserNameAndMailAddress(primaryAdmin);
|
||||
|
||||
// メール送信
|
||||
this.sendgridService.sendMailWithU117(
|
||||
context,
|
||||
authorEmail,
|
||||
typistEmail,
|
||||
authorName,
|
||||
task.file.file_name.replace('.zip', ''),
|
||||
typistName,
|
||||
primaryAdminName,
|
||||
);
|
||||
} catch (e) {
|
||||
// メール送信に関する例外はログだけ出して握りつぶす
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
if (e instanceof Error) {
|
||||
@ -400,6 +491,26 @@ export class TasksService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async getPrimaryAdminUser(
|
||||
context: Context,
|
||||
accountId: number,
|
||||
): Promise<User> {
|
||||
const accountInfo = await this.accountsRepository.findAccountById(
|
||||
context,
|
||||
accountId,
|
||||
);
|
||||
if (!accountInfo || !accountInfo.primary_admin_user_id) {
|
||||
throw new Error(`account or primary admin not found. id=${accountId}`);
|
||||
}
|
||||
|
||||
const primaryAdmin = await this.usersRepository.findUserById(
|
||||
context,
|
||||
accountInfo.primary_admin_user_id,
|
||||
);
|
||||
|
||||
return primaryAdmin;
|
||||
}
|
||||
/**
|
||||
* 指定した音声ファイルに紐づくタスクをキャンセルする
|
||||
* @param audioFileId
|
||||
|
||||
@ -15,6 +15,8 @@ import { Assignee } from '../types/types';
|
||||
import { UserGroupMember } from '../../../repositories/user_groups/entity/user_group_member.entity';
|
||||
import { NotificationhubService } from '../../../gateways/notificationhub/notificationhub.service';
|
||||
import { UserGroupsRepositoryService } from '../../../repositories/user_groups/user_groups.repository.service';
|
||||
import { AccountsRepositoryService } from '../../../repositories/accounts/accounts.repository.service';
|
||||
import { SendGridService } from '../../../gateways/sendgrid/sendgrid.service';
|
||||
|
||||
export type TasksRepositoryMockValue = {
|
||||
getTasksFromAccountId:
|
||||
@ -84,6 +86,12 @@ export const makeTasksServiceMock = async (
|
||||
return makeNotificationhubServiceMock(
|
||||
notificationhubServiceMockValue,
|
||||
);
|
||||
// メール送信でしか利用しておらず、テストする必要がないが、依存関係解決のため空オブジェクトを定義しておく。
|
||||
case AccountsRepositoryService:
|
||||
return {};
|
||||
// メール送信でしか利用しておらず、テストする必要がないが、依存関係解決のため空オブジェクトを定義しておく。
|
||||
case SendGridService:
|
||||
return {};
|
||||
}
|
||||
})
|
||||
.compile();
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ADB2C_SIGN_IN_TYPE } from "../../../constants";
|
||||
import { AdB2cUser } from "../types/types";
|
||||
import { ADB2C_SIGN_IN_TYPE } from '../../../constants';
|
||||
import { AdB2cUser } from '../types/types';
|
||||
|
||||
export const isPromiseRejectedResult = (
|
||||
data: unknown,
|
||||
@ -19,4 +19,4 @@ export const getUserNameAndMailAddress = (user: AdB2cUser) => {
|
||||
(identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
|
||||
)?.issuerAssignedId;
|
||||
return { displayName, emailAddress };
|
||||
}
|
||||
};
|
||||
|
||||
@ -7,14 +7,17 @@ import { Context } from '../../common/log';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import {
|
||||
PRIMARY_ADMIN_NAME,
|
||||
AUTHOR_NAME,
|
||||
CUSTOMER_NAME,
|
||||
DEALER_NAME,
|
||||
FILE_NAME,
|
||||
LICENSE_QUANTITY,
|
||||
PO_NUMBER,
|
||||
PRIMARY_ADMIN_NAME,
|
||||
TOP_URL,
|
||||
USER_EMAIL,
|
||||
USER_NAME,
|
||||
TYPIST_NAME,
|
||||
} from '../../templates/constants';
|
||||
|
||||
@Injectable()
|
||||
@ -39,6 +42,8 @@ export class SendGridService {
|
||||
private readonly templateU109Text: string;
|
||||
private readonly templateU111Html: string;
|
||||
private readonly templateU111Text: string;
|
||||
private readonly templateU117Html: string;
|
||||
private readonly templateU117Text: string;
|
||||
|
||||
constructor(private readonly configService: ConfigService) {
|
||||
this.appDomain = this.configService.getOrThrow<string>('APP_DOMAIN');
|
||||
@ -121,6 +126,15 @@ export class SendGridService {
|
||||
path.resolve(__dirname, `../../templates/template_U_111.txt`),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
this.templateU117Html = readFileSync(
|
||||
path.resolve(__dirname, `../../templates/template_U_117.html`),
|
||||
'utf-8',
|
||||
);
|
||||
this.templateU117Text = readFileSync(
|
||||
path.resolve(__dirname, `../../templates/template_U_117.txt`),
|
||||
'utf-8',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -588,10 +602,66 @@ export class SendGridService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* U-117のテンプレートを使用したメールを送信する
|
||||
* @param context
|
||||
* @param authorEmail 文字起こしファイルのAuthorのメールアドレス
|
||||
* @param typistEmail 文字起こしを行ったTypistのメールアドレス
|
||||
* @param authorName 文字起こしファイルのAuthorの名前
|
||||
* @param fileName 文字起こしファイルのファイル名
|
||||
* @param typistName 文字起こしを行ったTypistの名前
|
||||
* @param adminName アカウント管理者の名前(プライマリ)
|
||||
* @returns mail with u117
|
||||
*/
|
||||
async sendMailWithU117(
|
||||
context: Context,
|
||||
authorEmail: string,
|
||||
typistEmail: string,
|
||||
authorName: string,
|
||||
fileName: string,
|
||||
typistName: string,
|
||||
adminName: string,
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${this.sendMailWithU117.name}`,
|
||||
);
|
||||
try {
|
||||
const subject = 'Transcription Completion Notification [U-117]';
|
||||
|
||||
// メールの本文を作成する
|
||||
const html = this.templateU117Html
|
||||
.replaceAll(AUTHOR_NAME, authorName)
|
||||
.replaceAll(FILE_NAME, fileName)
|
||||
.replaceAll(TYPIST_NAME, typistName)
|
||||
.replaceAll(PRIMARY_ADMIN_NAME, adminName);
|
||||
const text = this.templateU117Text
|
||||
.replaceAll(AUTHOR_NAME, authorName)
|
||||
.replaceAll(FILE_NAME, fileName)
|
||||
.replaceAll(TYPIST_NAME, typistName)
|
||||
.replaceAll(PRIMARY_ADMIN_NAME, adminName);
|
||||
|
||||
// メールを送信する
|
||||
this.sendMail(
|
||||
context,
|
||||
[authorEmail, typistEmail],
|
||||
[],
|
||||
this.mailFrom,
|
||||
subject,
|
||||
text,
|
||||
html,
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${this.sendMailWithU117.name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* メールを送信する
|
||||
* @param context
|
||||
* @param to
|
||||
* @param cc
|
||||
* @param from
|
||||
* @param subject
|
||||
* @param text
|
||||
|
||||
@ -175,6 +175,37 @@ export class UsersRepositoryService {
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* AuthorIDをもとにユーザーを取得します。
|
||||
* AuthorIDがセットされていない場合や、ユーザーが存在しない場合はエラーを返します。
|
||||
* @param context
|
||||
* @param authorId 検索対象のAuthorID
|
||||
* @param accountId 検索対象のアカウントID
|
||||
* @returns user by author id
|
||||
*/
|
||||
async findUserByAuthorId(
|
||||
context: Context,
|
||||
authorId: string,
|
||||
accountId: number,
|
||||
): Promise<User> {
|
||||
if (!authorId) {
|
||||
throw new Error('authorId is not set.');
|
||||
}
|
||||
|
||||
const user = await this.dataSource.getRepository(User).findOne({
|
||||
where: {
|
||||
author_id: authorId,
|
||||
account_id: accountId,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new UserNotFoundError(`User not Found.`);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定したアカウントIDを持つアカウントのプライマリ管理者とセカンダリ管理者を取得する
|
||||
* @param context context
|
||||
|
||||
@ -6,3 +6,6 @@ export const TOP_URL = '$TOP_URL$';
|
||||
export const PRIMARY_ADMIN_NAME = '$PRIMARY_ADMIN_NAME$';
|
||||
export const USER_NAME = '$USER_NAME$';
|
||||
export const USER_EMAIL = '$USER_EMAIL$';
|
||||
export const AUTHOR_NAME = '$AUTHOR_NAME$';
|
||||
export const FILE_NAME = '$FILE_NAME$';
|
||||
export const TYPIST_NAME = '$TYPIST_NAME$';
|
||||
|
||||
58
dictation_server/src/templates/template_U_117.html
Normal file
58
dictation_server/src/templates/template_U_117.html
Normal file
@ -0,0 +1,58 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Transcription Completion Notification [U-117]</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<h3><English></h3>
|
||||
<p>Dear $AUTHOR_NAME$,</p>
|
||||
<p>
|
||||
The transcription of the dictation you uploaded to ODMS Cloud has been completed.<br />
|
||||
- Dictation file name: $FILE_NAME$<br />
|
||||
- Transcriptionist name: $TYPIST_NAME$
|
||||
</p>
|
||||
<p>
|
||||
If you need support regarding ODMS Cloud, please contact $PRIMARY_ADMIN_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><Deutsch></h3>
|
||||
<p>Sehr geehrte(r) $AUTHOR_NAME$,</p>
|
||||
<p>
|
||||
Die Transkription des Diktats, das Sie in die ODMS Cloud hochgeladen haben, ist abgeschlossen.<br />
|
||||
- Name der Diktatdatei: $FILE_NAME$<br />
|
||||
- Name des Transkriptionisten: $TYPIST_NAME$
|
||||
</p>
|
||||
<p>
|
||||
Wenn Sie Unterstützung bezüglich ODMS Cloud benötigen, wenden Sie sich bitte an $PRIMARY_ADMIN_NAME$.
|
||||
</p>
|
||||
<p>
|
||||
Wenn Sie diese E-Mail fälschlicherweise erhalten haben, löschen Sie diese E-Mail bitte aus Ihrem System.<br />
|
||||
Dies ist eine automatisch generierte E-Mail und dieses Postfach wird nicht überwacht. Bitte nicht antworten.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3><Français></h3>
|
||||
<p>Chère/Cher $AUTHOR_NAME$,</p>
|
||||
<p>
|
||||
La transcription de la dictée que vous avez téléchargée sur ODMS Cloud est terminée.<br />
|
||||
- Nom du fichier de dictée: $FILE_NAME$<br />
|
||||
- Nom du transcriptionniste: $TYPIST_NAME$
|
||||
</p>
|
||||
<p>
|
||||
Si vous avez besoin d'assistance concernant ODMS Cloud, veuillez contacter $PRIMARY_ADMIN_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>
|
||||
38
dictation_server/src/templates/template_U_117.txt
Normal file
38
dictation_server/src/templates/template_U_117.txt
Normal file
@ -0,0 +1,38 @@
|
||||
<English>
|
||||
|
||||
Dear $AUTHOR_NAME$,
|
||||
|
||||
The transcription of the dictation you uploaded to ODMS Cloud has been completed.
|
||||
- Dictation file name: $FILE_NAME$
|
||||
- Transcriptionist name: $TYPIST_NAME$
|
||||
|
||||
If you need support regarding ODMS Cloud, please contact $PRIMARY_ADMIN_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) $AUTHOR_NAME$,
|
||||
|
||||
Die Transkription des Diktats, das Sie in die ODMS Cloud hochgeladen haben, ist abgeschlossen.
|
||||
- Name der Diktatdatei: $FILE_NAME$
|
||||
- Name des Transkriptionisten: $TYPIST_NAME$
|
||||
|
||||
Wenn Sie Unterstützung bezüglich ODMS Cloud benötigen, wenden Sie sich bitte an $PRIMARY_ADMIN_NAME$.
|
||||
|
||||
Wenn Sie diese E-Mail fälschlicherweise erhalten haben, löschen Sie diese E-Mail bitte aus Ihrem System.
|
||||
Dies ist eine automatisch generierte E-Mail und dieses Postfach wird nicht überwacht. Bitte nicht antworten.
|
||||
|
||||
<Français>
|
||||
|
||||
Chère/Cher $AUTHOR_NAME$,
|
||||
|
||||
La transcription de la dictée que vous avez téléchargée sur ODMS Cloud est terminée.
|
||||
- Nom du fichier de dictée: $FILE_NAME$
|
||||
- Nom du transcriptionniste: $TYPIST_NAME$
|
||||
|
||||
Si vous avez besoin d'assistance concernant ODMS Cloud, veuillez contacter $PRIMARY_ADMIN_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.
|
||||
Loading…
x
Reference in New Issue
Block a user