Merge branch 'develop'
This commit is contained in:
commit
29d875765e
@ -1,8 +1,6 @@
|
||||
import AppRouter from "AppRouter";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import { PublicClientApplication } from "@azure/msal-browser";
|
||||
import { MsalProvider, useMsal } from "@azure/msal-react";
|
||||
import { msalConfig } from "common/msalConfig";
|
||||
import { useMsal } from "@azure/msal-react";
|
||||
import { useEffect, useLayoutEffect } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import globalAxios, { AxiosError, AxiosResponse } from "axios";
|
||||
@ -19,7 +17,6 @@ const App = (): JSX.Element => {
|
||||
const { instance } = useMsal();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [t, i18n] = useTranslation();
|
||||
const pca = new PublicClientApplication(msalConfig);
|
||||
useEffect(() => {
|
||||
const id = globalAxios.interceptors.response.use(
|
||||
(response: AxiosResponse) => response,
|
||||
@ -70,11 +67,9 @@ const App = (): JSX.Element => {
|
||||
dispatch(closeSnackbar());
|
||||
}}
|
||||
/>
|
||||
<MsalProvider instance={pca}>
|
||||
<BrowserRouter>
|
||||
<AppRouter />
|
||||
</BrowserRouter>
|
||||
</MsalProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -76,3 +76,16 @@ export const getIdTokenFromLocalStorage = (
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// JWTが有効期限切れかどうかを判定する
|
||||
export const isTokenExpired = (token: string | null): boolean => {
|
||||
if (token == null) {
|
||||
return true;
|
||||
}
|
||||
const tokenObject = JSON.parse(atob(token.split(".")[1]));
|
||||
if (isToken(tokenObject)) {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
return tokenObject.exp < now;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
@ -3,18 +3,25 @@ import React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import { Provider } from "react-redux";
|
||||
import { PublicClientApplication } from "@azure/msal-browser";
|
||||
import { msalConfig } from "common/msalConfig";
|
||||
import { MsalProvider } from "@azure/msal-react";
|
||||
import App from "./App";
|
||||
import * as serviceWorker from "./serviceWorker";
|
||||
import i18n from "./i18n";
|
||||
|
||||
const pca = new PublicClientApplication(msalConfig);
|
||||
|
||||
const container = document.getElementById("root");
|
||||
if (container) {
|
||||
const root = createRoot(container);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<Provider store={store}>
|
||||
<MsalProvider instance={pca}>
|
||||
<I18nextProvider i18n={i18n} />
|
||||
<App />
|
||||
</MsalProvider>
|
||||
</Provider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
@ -10,14 +10,6 @@ import {
|
||||
import React, { useEffect } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
clearToken,
|
||||
isAdminUser,
|
||||
isApproveTier,
|
||||
isStandardUser,
|
||||
loadAccessToken,
|
||||
} from "features/auth";
|
||||
import { TIERS } from "components/auth/constants";
|
||||
|
||||
const AuthPage: React.FC = (): JSX.Element => {
|
||||
const { instance } = useMsal();
|
||||
@ -34,38 +26,7 @@ const AuthPage: React.FC = (): JSX.Element => {
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
// ログイン済みの場合、ログイン後の遷移先を決定する
|
||||
if (loadAccessToken()) {
|
||||
// 第一~第四階層の管理者はライセンス画面へ遷移
|
||||
if (
|
||||
isApproveTier([
|
||||
TIERS.TIER1,
|
||||
TIERS.TIER2,
|
||||
TIERS.TIER3,
|
||||
TIERS.TIER4,
|
||||
]) &&
|
||||
isAdminUser()
|
||||
) {
|
||||
navigate("/license");
|
||||
return;
|
||||
}
|
||||
// 第五階層の管理者はユーザー画面へ遷移
|
||||
if (isApproveTier([TIERS.TIER5]) && isAdminUser()) {
|
||||
navigate("/user");
|
||||
return;
|
||||
}
|
||||
// 一般ユーザーはdictationPageへ遷移
|
||||
if (isStandardUser()) {
|
||||
navigate("/dictations");
|
||||
return;
|
||||
}
|
||||
// それ以外は認証エラー画面へ遷移
|
||||
instance.logoutRedirect({
|
||||
postLogoutRedirectUri: "/AuthError",
|
||||
});
|
||||
clearToken();
|
||||
return;
|
||||
}
|
||||
// idTokenが有効セットされているかを確認する
|
||||
const loginResult = await instance.handleRedirectPromise();
|
||||
if (loginResult && loginResult.account) {
|
||||
const { homeAccountId, idTokenClaims } = loginResult.account;
|
||||
|
||||
@ -353,6 +353,7 @@ export const DisPlayInfo: React.FC = (): JSX.Element => {
|
||||
<li>
|
||||
<label htmlFor="comment">
|
||||
<input
|
||||
id="comment"
|
||||
type="checkbox"
|
||||
value="clm16"
|
||||
className={styles.formCheck}
|
||||
|
||||
@ -1116,13 +1116,7 @@ const DictationPage: React.FC = (): JSX.Element => {
|
||||
{(isChangeTranscriptionistPopupOpen || !isLoading) &&
|
||||
tasks.length !== 0 &&
|
||||
tasks.map((x) => (
|
||||
<tr
|
||||
key={x.audioFileId}
|
||||
style={{
|
||||
backgroundColor:
|
||||
x.priority === "01" ? "#ff00004f" : "#ffffff",
|
||||
}}
|
||||
>
|
||||
<tr key={x.audioFileId}>
|
||||
<td className={styles.clm0}>
|
||||
<ul className={styles.menuInTable}>
|
||||
<li>
|
||||
@ -1231,7 +1225,12 @@ const DictationPage: React.FC = (): JSX.Element => {
|
||||
</td>
|
||||
)}
|
||||
{displayColumn.Priority && (
|
||||
<td className={styles.clm3}>
|
||||
<td
|
||||
className={styles.clm3}
|
||||
style={{
|
||||
color: x.priority === "01" ? "red" : undefined,
|
||||
}}
|
||||
>
|
||||
{x.priority === "01"
|
||||
? PRIORITY.HIGH
|
||||
: PRIORITY.NORMAL}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export const AuthErrorPage = (): JSX.Element => (
|
||||
<div>
|
||||
<p>ログインに失敗しました</p>
|
||||
<p>login failed</p>
|
||||
<br />
|
||||
<Link to="/">return to TopPage</Link>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -171,8 +171,11 @@ export const LicenseOrderHistory: React.FC<LicenseOrderHistoryProps> = (
|
||||
<div>
|
||||
<div className={styles.pageHeader}>
|
||||
<h1 className={styles.pageTitle}>
|
||||
{t(getTranslationID("orderHistoriesPage.label.title"))}
|
||||
{t(getTranslationID("LicenseSummaryPage.label.title"))}
|
||||
</h1>
|
||||
<p className={styles.pageTx}>
|
||||
{t(getTranslationID("orderHistoriesPage.label.orderHistory"))}
|
||||
</p>
|
||||
</div>
|
||||
<section className={styles.license}>
|
||||
<div>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useMsal } from "@azure/msal-react";
|
||||
import { AppDispatch } from "app/store";
|
||||
import { isIdToken } from "common/token";
|
||||
import { isIdToken, isTokenExpired } from "common/token";
|
||||
import {
|
||||
clearToken,
|
||||
isAdminUser,
|
||||
@ -52,10 +52,10 @@ const LoginPage: React.FC = (): JSX.Element => {
|
||||
instance.logoutRedirect({
|
||||
postLogoutRedirectUri: "/AuthError",
|
||||
});
|
||||
clearToken();
|
||||
}, [instance, navigate]);
|
||||
dispatch(clearToken());
|
||||
}, [instance, navigate, dispatch]);
|
||||
|
||||
const tokenSet = useCallback(
|
||||
const tokenSetAndNavigate = useCallback(
|
||||
async (idToken: string) => {
|
||||
// ログイン処理呼び出し
|
||||
const { meta, payload } = await dispatch(loginAsync({ idToken }));
|
||||
@ -96,31 +96,32 @@ const LoginPage: React.FC = (): JSX.Element => {
|
||||
|
||||
useEffect(() => {
|
||||
// idTokenStringがあるか⇒認証中
|
||||
// accessTokenがある場合⇒ログイン済み
|
||||
// どちらもなければ直打ち
|
||||
// accessTokenがある場合⇒ログイン済みなのにブラウザバックでログイン画面に戻ってきた場合
|
||||
// どちらもなければURL直打ち
|
||||
(async () => {
|
||||
if (loadAccessToken()) {
|
||||
navigateToLoginedPage();
|
||||
return;
|
||||
// ローカルストレージにidTokenがある場合は取得する
|
||||
let idTokenString: string | null = null;
|
||||
if (localStorageKeyforIdToken !== null) {
|
||||
idTokenString = localStorage.getItem(localStorageKeyforIdToken);
|
||||
}
|
||||
|
||||
// AADB2Cのログイン画面とLoginPageを経由していない場合はトップページに遷移する
|
||||
if (!localStorageKeyforIdToken) {
|
||||
navigate("/");
|
||||
return;
|
||||
}
|
||||
const idTokenString = localStorage.getItem(localStorageKeyforIdToken);
|
||||
|
||||
// idTokenがない(=正常なログインプロセス中でない)場合は有効なアクセストークンを所持しているか確認し、
|
||||
// 有効であればログイン画面に遷移 or 無効であればトップページに遷移
|
||||
if (idTokenString === null) {
|
||||
const token = loadAccessToken();
|
||||
// アクセストークンがない or 有効期限切れ場合はトップページに遷移
|
||||
if (isTokenExpired(token)) {
|
||||
navigate("/");
|
||||
} else {
|
||||
// 有効なアクセストークンがある場合はログイン画面に遷移
|
||||
navigateToLoginedPage();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (idTokenString) {
|
||||
const idTokenObject = JSON.parse(idTokenString);
|
||||
if (isIdToken(idTokenObject)) {
|
||||
await tokenSet(idTokenObject.secret);
|
||||
}
|
||||
await tokenSetAndNavigate(idTokenObject.secret);
|
||||
}
|
||||
})();
|
||||
// 画面描画後のみ実行するため引数を設定しない
|
||||
|
||||
@ -147,12 +147,17 @@ const UserListPage: React.FC = (): JSX.Element => {
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div className={styles.tableWrap}>
|
||||
<table className={`${styles.table} ${styles.user}`}>
|
||||
<tbody>
|
||||
<tr className={styles.tableHeader}>
|
||||
<th className={styles.clm0}>{/** th is empty */}</th>
|
||||
<th>{t(getTranslationID("userListPage.label.name"))}</th>
|
||||
<th>{t(getTranslationID("userListPage.label.role"))}</th>
|
||||
<th>
|
||||
{t(getTranslationID("userListPage.label.name"))}
|
||||
</th>
|
||||
<th>
|
||||
{t(getTranslationID("userListPage.label.role"))}
|
||||
</th>
|
||||
<th>
|
||||
{t(getTranslationID("userListPage.label.authorID"))}
|
||||
</th>
|
||||
@ -163,9 +168,13 @@ const UserListPage: React.FC = (): JSX.Element => {
|
||||
{t(getTranslationID("userListPage.label.prompt"))}
|
||||
</th>
|
||||
<th>
|
||||
{t(getTranslationID("userListPage.label.typistGroup"))}
|
||||
{t(
|
||||
getTranslationID("userListPage.label.typistGroup")
|
||||
)}
|
||||
</th>
|
||||
<th>
|
||||
{t(getTranslationID("userListPage.label.email"))}
|
||||
</th>
|
||||
<th>{t(getTranslationID("userListPage.label.email"))}</th>
|
||||
<th>
|
||||
{t(getTranslationID("userListPage.label.status"))}
|
||||
</th>
|
||||
@ -179,7 +188,9 @@ const UserListPage: React.FC = (): JSX.Element => {
|
||||
{t(getTranslationID("userListPage.label.autoRenew"))}
|
||||
</th>
|
||||
<th>
|
||||
{t(getTranslationID("userListPage.label.notification"))}
|
||||
{t(
|
||||
getTranslationID("userListPage.label.notification")
|
||||
)}
|
||||
</th>
|
||||
<th>
|
||||
{t(
|
||||
@ -306,6 +317,7 @@ const UserListPage: React.FC = (): JSX.Element => {
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{!isLoading && users.length === 0 && (
|
||||
<p
|
||||
style={{
|
||||
|
||||
@ -128,19 +128,26 @@ export const EditOptionItemsPopup: React.FC<EditOptionItemsPopupProps> = (
|
||||
<dl className={`${styles.formList} ${styles.hasbg}`}>
|
||||
<dd className={styles.full}>
|
||||
<div className={styles.tableWrap}>
|
||||
<table className={styles.table}>
|
||||
<table className={`${styles.table} ${styles.optionItem}`}>
|
||||
<tbody>
|
||||
<tr className={styles.tableHeader}>
|
||||
<th className={styles.noLine}>
|
||||
{t(getTranslationID("worktypeIdSetting.label.itemLabel"))}
|
||||
{t(
|
||||
getTranslationID("worktypeIdSetting.label.itemLabel")
|
||||
)}
|
||||
</th>
|
||||
<th className={styles.noLine}>
|
||||
{t(
|
||||
getTranslationID("worktypeIdSetting.label.defaultValue")
|
||||
getTranslationID(
|
||||
"worktypeIdSetting.label.defaultValue"
|
||||
)
|
||||
)}
|
||||
</th>
|
||||
<th>
|
||||
{t(
|
||||
getTranslationID("worktypeIdSetting.label.initialValue")
|
||||
getTranslationID(
|
||||
"worktypeIdSetting.label.initialValue"
|
||||
)
|
||||
)}
|
||||
</th>
|
||||
</tr>
|
||||
@ -188,9 +195,13 @@ export const EditOptionItemsPopup: React.FC<EditOptionItemsPopupProps> = (
|
||||
)
|
||||
)}
|
||||
</option>
|
||||
<option value={OPTION_ITEMS_DEFAULT_VALUE_TYPE.BLANK}>
|
||||
<option
|
||||
value={OPTION_ITEMS_DEFAULT_VALUE_TYPE.BLANK}
|
||||
>
|
||||
{t(
|
||||
getTranslationID("worktypeIdSetting.label.blank")
|
||||
getTranslationID(
|
||||
"worktypeIdSetting.label.blank"
|
||||
)
|
||||
)}
|
||||
</option>
|
||||
<option
|
||||
@ -229,6 +240,20 @@ export const EditOptionItemsPopup: React.FC<EditOptionItemsPopupProps> = (
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
<tr>
|
||||
<td colSpan={3}>
|
||||
<span
|
||||
className={`${styles.formComment} ${styles.alignCenter}`}
|
||||
>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"worktypeIdSetting.label.optionItemTerms"
|
||||
)
|
||||
)}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{isPushSaveButton && hasInvalidOptionItems && (
|
||||
<span className={`${styles.formError} ${styles.alignCenter}`}>
|
||||
@ -248,14 +273,6 @@ export const EditOptionItemsPopup: React.FC<EditOptionItemsPopupProps> = (
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
style={{ display: "block" }}
|
||||
className={`${styles.formComment} ${styles.alignCenter}`}
|
||||
>
|
||||
{t(
|
||||
getTranslationID("worktypeIdSetting.label.optionItemTerms")
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</dd>
|
||||
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||
|
||||
@ -1130,6 +1130,9 @@ h3 + .brCrumb .tlIcon {
|
||||
.modal .form .table.backup td:first-child {
|
||||
padding: 0.6rem 0.2rem;
|
||||
}
|
||||
.modal .form .table.optionItem select {
|
||||
width: 123px;
|
||||
}
|
||||
.modal .form .pagenation {
|
||||
margin-bottom: 1.5rem;
|
||||
padding-right: 2.5%;
|
||||
@ -1597,15 +1600,6 @@ _:-ms-lang(x)::-ms-backdrop,
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
.account .table.user,
|
||||
.user .table.user,
|
||||
.license .table.user,
|
||||
.dictation .table.user,
|
||||
.partners .table.user,
|
||||
.workflow .table.user,
|
||||
.support .table.user {
|
||||
margin-bottom: 5rem;
|
||||
}
|
||||
.account .table.user th::after,
|
||||
.user .table.user th::after,
|
||||
.license .table.user th::after,
|
||||
@ -1626,6 +1620,16 @@ _:-ms-lang(x)::-ms-backdrop,
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.user .table {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.user .tableWrap {
|
||||
max-width: calc(100vw - 5.1rem);
|
||||
max-height: 90vh;
|
||||
overflow-x: scroll;
|
||||
margin-bottom: 5rem;
|
||||
}
|
||||
|
||||
.account .listVertical {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
@ -2488,10 +2492,13 @@ tr.isSelected .menuInTable li a.isDisable {
|
||||
.formChange ul.chooseMember li input + label,
|
||||
.formChange ul.holdMember li input + label {
|
||||
display: block;
|
||||
padding: 0.2rem 0 0.2rem 1.5rem;
|
||||
padding: 0.4rem 0 0.4rem 1.5rem;
|
||||
margin-right: 0;
|
||||
background: url(../assets/images/circle.svg) no-repeat left center;
|
||||
background-size: 1.3rem;
|
||||
white-space: pre-line;
|
||||
word-break: break-all;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.formChange ul.chooseMember li input + label:hover,
|
||||
.formChange ul.holdMember li input + label:hover {
|
||||
@ -2501,7 +2508,7 @@ tr.isSelected .menuInTable li a.isDisable {
|
||||
}
|
||||
.formChange ul.chooseMember li input:checked + label,
|
||||
.formChange ul.holdMember li input:checked + label {
|
||||
padding: 0.2rem 1rem 0.2rem 0;
|
||||
padding: 0.4rem 1.5rem 0.4rem 0;
|
||||
background: url(../assets/images/check_circle_fill.svg) no-repeat right center;
|
||||
background-size: 1.3rem;
|
||||
}
|
||||
|
||||
@ -74,6 +74,7 @@ declare const classNames: {
|
||||
readonly table: "table";
|
||||
readonly tableHeader: "tableHeader";
|
||||
readonly backup: "backup";
|
||||
readonly optionItem: "optionItem";
|
||||
readonly pagenation: "pagenation";
|
||||
readonly encryptionPass: "encryptionPass";
|
||||
readonly pageHeader: "pageHeader";
|
||||
|
||||
@ -40,9 +40,6 @@ export class User {
|
||||
@Column({ default: true })
|
||||
auto_renew: boolean;
|
||||
|
||||
@Column({ default: true })
|
||||
license_alert: boolean;
|
||||
|
||||
@Column({ default: true })
|
||||
notification: boolean;
|
||||
|
||||
|
||||
@ -48,7 +48,6 @@ export const makeTestUser = async (
|
||||
accepted_dpa_version: d?.accepted_dpa_version ?? "1.0",
|
||||
email_verified: d?.email_verified ?? true,
|
||||
auto_renew: d?.auto_renew ?? true,
|
||||
license_alert: d?.license_alert ?? true,
|
||||
notification: d?.notification ?? true,
|
||||
encryption: d?.encryption ?? true,
|
||||
encryption_password: d?.encryption_password,
|
||||
@ -117,7 +116,6 @@ export const makeTestAccount = async (
|
||||
accepted_dpa_version: d?.accepted_dpa_version ?? "1.0",
|
||||
email_verified: d?.email_verified ?? true,
|
||||
auto_renew: d?.auto_renew ?? true,
|
||||
license_alert: d?.license_alert ?? true,
|
||||
notification: d?.notification ?? true,
|
||||
encryption: d?.encryption ?? true,
|
||||
encryption_password: d?.encryption_password ?? "password",
|
||||
|
||||
@ -11,6 +11,7 @@ import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
import { TasksService } from './tasks.service';
|
||||
import { DataSource } from 'typeorm';
|
||||
import {
|
||||
createAudioFile,
|
||||
createCheckoutPermissions,
|
||||
createTask,
|
||||
createUserGroup,
|
||||
@ -703,6 +704,19 @@ describe('TasksService', () => {
|
||||
role: 'author',
|
||||
author_id: 'MY_AUTHOR_ID',
|
||||
});
|
||||
|
||||
//「バグ 3661: [FB対応]Option Itemにチェックを付けると真っ白な画面になる」の確認のため
|
||||
// audio_file_idをTaskIdと異なる値にするために、AudioFileを作成
|
||||
await createAudioFile(
|
||||
source,
|
||||
accountId,
|
||||
userId,
|
||||
'MY_AUTHOR_ID',
|
||||
'',
|
||||
'00',
|
||||
);
|
||||
|
||||
// Taskを作成
|
||||
await createTask(
|
||||
source,
|
||||
accountId,
|
||||
@ -746,10 +760,26 @@ describe('TasksService', () => {
|
||||
{
|
||||
const task = tasks[0];
|
||||
expect(task.jobNumber).toEqual('00000001');
|
||||
// AudioOptionItem
|
||||
const audioOptionItems = Array.from({ length: 10 }).map((_, i) => {
|
||||
return {
|
||||
optionItemLabel: `label${i}:audio_file_id${task.audioFileId}`,
|
||||
optionItemValue: `value${i}:audio_file_id${task.audioFileId}`,
|
||||
};
|
||||
});
|
||||
expect(task.optionItemList).toEqual(audioOptionItems);
|
||||
}
|
||||
{
|
||||
const task = tasks[1];
|
||||
expect(task.jobNumber).toEqual('00000002');
|
||||
// AudioOptionItem
|
||||
const audioOptionItems = Array.from({ length: 10 }).map((_, i) => {
|
||||
return {
|
||||
optionItemLabel: `label${i}:audio_file_id${task.audioFileId}`,
|
||||
optionItemValue: `value${i}:audio_file_id${task.audioFileId}`,
|
||||
};
|
||||
});
|
||||
expect(task.optionItemList).toEqual(audioOptionItems);
|
||||
}
|
||||
});
|
||||
it('[Author] Authorは同一アカウントであっても自分以外のAuhtorのTaskは取得できない', async () => {
|
||||
|
||||
@ -38,6 +38,7 @@ import {
|
||||
NotificationhubServiceMockValue,
|
||||
makeNotificationhubServiceMock,
|
||||
} from './tasks.service.mock';
|
||||
import { AudioOptionItem } from '../../../repositories/audio_option_items/entity/audio_option_item.entity';
|
||||
|
||||
export const makeTaskTestingModuleWithNotificaiton = async (
|
||||
datasource: DataSource,
|
||||
@ -130,6 +131,18 @@ export const createTask = async (
|
||||
audio_format: 'audio_format',
|
||||
is_encrypted: true,
|
||||
});
|
||||
// AudioOptionItemを10個作成
|
||||
const audioOptionItems = Array.from({ length: 10 }).map((_, i) => {
|
||||
return {
|
||||
audio_file_id: audioFileIdentifiers[0].id,
|
||||
label: `label${i}:audio_file_id${audioFileIdentifiers[0].id}`,
|
||||
value: `value${i}:audio_file_id${audioFileIdentifiers[0].id}`,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
await datasource.getRepository(AudioOptionItem).insert(audioOptionItems);
|
||||
|
||||
const audioFile = audioFileIdentifiers.pop() as AudioFile;
|
||||
const { identifiers: taskIdentifiers } = await datasource
|
||||
.getRepository(Task)
|
||||
@ -147,6 +160,37 @@ export const createTask = async (
|
||||
const task = taskIdentifiers.pop() as Task;
|
||||
return { taskId: task.id, audioFileId: audioFile.id };
|
||||
};
|
||||
|
||||
export const createAudioFile = async(
|
||||
datasource: DataSource,
|
||||
account_id: number,
|
||||
owner_user_id: number,
|
||||
author_id: string,
|
||||
work_type_id: string,
|
||||
priority: string,
|
||||
): Promise<{ audioFileId: number }> => {
|
||||
const { identifiers: audioFileIdentifiers } = await datasource
|
||||
.getRepository(AudioFile)
|
||||
.insert({
|
||||
account_id: account_id,
|
||||
owner_user_id: owner_user_id,
|
||||
url: '',
|
||||
file_name: 'x.zip',
|
||||
author_id: author_id,
|
||||
work_type_id: work_type_id,
|
||||
started_at: new Date(),
|
||||
duration: '100000',
|
||||
finished_at: new Date(),
|
||||
uploaded_at: new Date(),
|
||||
file_size: 10000,
|
||||
priority: priority,
|
||||
audio_format: 'audio_format',
|
||||
is_encrypted: true,
|
||||
});
|
||||
const audioFile = audioFileIdentifiers.pop() as AudioFile;
|
||||
return { audioFileId: audioFile.id };
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param datasource
|
||||
|
||||
@ -1073,12 +1073,12 @@ export class UsersService {
|
||||
const { parent_account_id: dealerId } =
|
||||
await this.accountsRepository.findAccountById(context, accountId);
|
||||
|
||||
if (dealerId == null) {
|
||||
throw new Error(`dealer is null. account_id=${accountId}`);
|
||||
}
|
||||
|
||||
const { company_name: dealerName } =
|
||||
let dealerName: string | null = null;
|
||||
if (dealerId !== null) {
|
||||
const { company_name } =
|
||||
await this.accountsRepository.findAccountById(context, dealerId);
|
||||
dealerName = company_name;
|
||||
}
|
||||
|
||||
const { companyName, adminEmails } = await this.getAccountInformation(
|
||||
context,
|
||||
|
||||
@ -41,6 +41,9 @@ export class SendGridService {
|
||||
private readonly templateU107Text: string;
|
||||
private readonly templateU108Html: string;
|
||||
private readonly templateU108Text: string;
|
||||
// U-108のテンプレート差分(親アカウントがない場合)
|
||||
private readonly templateU108NoParentHtml: string;
|
||||
private readonly templateU108NoParentText: string;
|
||||
private readonly templateU109Html: string;
|
||||
private readonly templateU109Text: string;
|
||||
private readonly templateU111Html: string;
|
||||
@ -118,6 +121,17 @@ export class SendGridService {
|
||||
path.resolve(__dirname, `../../templates/template_U_108.txt`),
|
||||
'utf-8',
|
||||
);
|
||||
this.templateU108NoParentHtml = readFileSync(
|
||||
path.resolve(
|
||||
__dirname,
|
||||
`../../templates/template_U_108_no_parent.html`,
|
||||
),
|
||||
'utf-8',
|
||||
);
|
||||
this.templateU108NoParentText = readFileSync(
|
||||
path.resolve(__dirname, `../../templates/template_U_108_no_parent.txt`),
|
||||
'utf-8',
|
||||
);
|
||||
this.templateU109Html = readFileSync(
|
||||
path.resolve(__dirname, `../../templates/template_U_109.html`),
|
||||
'utf-8',
|
||||
@ -462,7 +476,7 @@ export class SendGridService {
|
||||
userMail: string,
|
||||
customerAdminMails: string[],
|
||||
customerAccountName: string,
|
||||
dealerAccountName: string,
|
||||
dealerAccountName: string | null,
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${this.sendMailWithU108.name}`,
|
||||
@ -471,19 +485,34 @@ export class SendGridService {
|
||||
const subject = 'License Assigned Notification [U-108]';
|
||||
const url = new URL(this.appDomain).href;
|
||||
|
||||
// メールの本文を作成する
|
||||
const html = this.templateU108Html
|
||||
let html: string;
|
||||
let text: string;
|
||||
|
||||
if (dealerAccountName === null) {
|
||||
html = this.templateU108NoParentHtml
|
||||
.replaceAll(CUSTOMER_NAME, customerAccountName)
|
||||
.replaceAll(USER_NAME, userName)
|
||||
.replaceAll(USER_EMAIL, userMail)
|
||||
.replaceAll(TOP_URL, url);
|
||||
text = this.templateU108NoParentText
|
||||
.replaceAll(CUSTOMER_NAME, customerAccountName)
|
||||
.replaceAll(USER_NAME, userName)
|
||||
.replaceAll(USER_EMAIL, userMail)
|
||||
.replaceAll(TOP_URL, url);
|
||||
} else {
|
||||
html = this.templateU108Html
|
||||
.replaceAll(CUSTOMER_NAME, customerAccountName)
|
||||
.replaceAll(DEALER_NAME, dealerAccountName)
|
||||
.replaceAll(USER_NAME, userName)
|
||||
.replaceAll(USER_EMAIL, userMail)
|
||||
.replaceAll(TOP_URL, url);
|
||||
const text = this.templateU108Text
|
||||
text = this.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];
|
||||
|
||||
|
||||
@ -18,6 +18,6 @@ export class AudioOptionItem {
|
||||
@Column()
|
||||
value: string;
|
||||
@ManyToOne(() => Task, (task) => task.audio_file_id)
|
||||
@JoinColumn({ name: 'audio_file_id' })
|
||||
@JoinColumn({ name: 'audio_file_id', referencedColumnName: 'audio_file_id' })
|
||||
task: Task | null;
|
||||
}
|
||||
|
||||
69
dictation_server/src/templates/template_U_108_no_parent.html
Normal file
69
dictation_server/src/templates/template_U_108_no_parent.html
Normal file
@ -0,0 +1,69 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>License Assigned Notification [U-108]</title>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<h3><English></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><Deutsch></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 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 $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>
|
||||
41
dictation_server/src/templates/template_U_108_no_parent.txt
Normal file
41
dictation_server/src/templates/template_U_108_no_parent.txt
Normal 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 fälschlicherweise erhalten haben, löschen Sie diese E-Mail bitte aus Ihrem System.
|
||||
Dies ist eine automatisch generierte E-Mail und dieses Postfach wird nicht überwacht. Bitte nicht antworten.
|
||||
|
||||
<Français>
|
||||
|
||||
Chère/Cher $CUSTOMER_NAME$,
|
||||
|
||||
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.
|
||||
Loading…
x
Reference in New Issue
Block a user