Merge branch 'develop'
This commit is contained in:
commit
f978d837e7
4
db/init/init_accounts_auto_increment.sql
Normal file
4
db/init/init_accounts_auto_increment.sql
Normal file
@ -0,0 +1,4 @@
|
||||
-- [OMDS_IS-231] アカウントIDの開始番号調整 | 課題の表示 | Backlog 対応
|
||||
-- IDからアカウント数が推測されるため、ユーザ指定の任意値を最初の番号とする
|
||||
-- 一度しか実行しないため、migrate fileではなくDBの初期値として扱う。移行時の実行を想定
|
||||
ALTER TABLE accounts AUTO_INCREMENT = 853211;
|
||||
@ -54,6 +54,8 @@ export const errorCodes = [
|
||||
"E010809", // ライセンス発行キャンセル不可エラー(ステータスが変えられている場合)
|
||||
"E010810", // ライセンス発行キャンセル不可エラー(発行から一定期間経過した場合)
|
||||
"E010811", // ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合)
|
||||
"E010908", // タイピストグループ不在エラー
|
||||
"E010909", // タイピストグループ名重複エラー
|
||||
"E011001", // ワークタイプ重複エラー
|
||||
"E011002", // ワークタイプ登録上限超過エラー
|
||||
"E011003", // ワークタイプ不在エラー
|
||||
|
||||
@ -43,7 +43,12 @@ export const UNAUTHORIZED_TO_CONTINUE_ERROR_CODES = [
|
||||
* ローカルストレージに残すキー類
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const KEYS_TO_PRESERVE = ["accessToken", "refreshToken", "displayInfo"];
|
||||
export const KEYS_TO_PRESERVE = [
|
||||
"accessToken",
|
||||
"refreshToken",
|
||||
"displayInfo",
|
||||
"sortCriteria",
|
||||
];
|
||||
|
||||
/**
|
||||
* アクセストークンを更新する基準の秒数
|
||||
|
||||
@ -28,6 +28,13 @@ export const SORTABLE_COLUMN = {
|
||||
export type SortableColumnType =
|
||||
typeof SORTABLE_COLUMN[keyof typeof SORTABLE_COLUMN];
|
||||
|
||||
export const isSortableColumnType = (
|
||||
value: string
|
||||
): value is SortableColumnType => {
|
||||
const arg = value as SortableColumnType;
|
||||
return Object.values(SORTABLE_COLUMN).includes(arg);
|
||||
};
|
||||
|
||||
export type SortableColumnList =
|
||||
typeof SORTABLE_COLUMN[keyof typeof SORTABLE_COLUMN];
|
||||
|
||||
@ -38,6 +45,10 @@ export const DIRECTION = {
|
||||
|
||||
export type DirectionType = typeof DIRECTION[keyof typeof DIRECTION];
|
||||
|
||||
// DirectionTypeの型チェック関数
|
||||
export const isDirectionType = (arg: string): arg is DirectionType =>
|
||||
arg in DIRECTION;
|
||||
|
||||
export interface DisplayInfoType {
|
||||
JobNumber: boolean;
|
||||
Status: boolean;
|
||||
|
||||
@ -280,7 +280,6 @@ export const playbackAsync = createAsyncThunk<
|
||||
direction: DirectionType;
|
||||
paramName: SortableColumnType;
|
||||
audioFileId: number;
|
||||
isTypist: boolean;
|
||||
},
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
@ -289,7 +288,7 @@ export const playbackAsync = createAsyncThunk<
|
||||
};
|
||||
}
|
||||
>("dictations/playbackAsync", async (args, thunkApi) => {
|
||||
const { audioFileId, direction, paramName, isTypist } = args;
|
||||
const { audioFileId, direction, paramName } = args;
|
||||
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
@ -300,15 +299,12 @@ export const playbackAsync = createAsyncThunk<
|
||||
const tasksApi = new TasksApi(config);
|
||||
const usersApi = new UsersApi(config);
|
||||
try {
|
||||
// ユーザーがタイピストである場合に、ソート条件を保存する
|
||||
if (isTypist) {
|
||||
await usersApi.updateSortCriteria(
|
||||
{ direction, paramName },
|
||||
{
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
}
|
||||
);
|
||||
}
|
||||
await usersApi.updateSortCriteria(
|
||||
{ direction, paramName },
|
||||
{
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
}
|
||||
);
|
||||
await tasksApi.checkout(audioFileId, {
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
|
||||
@ -62,6 +62,12 @@ export const orderLicenseAsync = createAsyncThunk<
|
||||
);
|
||||
}
|
||||
|
||||
if (error.code === "E010501") {
|
||||
errorMessage = getTranslationID(
|
||||
"licenseOrderPage.message.dealerNotFoundError"
|
||||
);
|
||||
}
|
||||
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
|
||||
@ -122,11 +122,17 @@ export const createTypistGroupAsync = createAsyncThunk<
|
||||
} catch (e) {
|
||||
// e ⇒ errorObjectに変換"
|
||||
const error = createErrorObject(e);
|
||||
|
||||
const message =
|
||||
error.statusCode === 400
|
||||
? getTranslationID("typistGroupSetting.message.groupSaveFailedError")
|
||||
: getTranslationID("common.message.internalServerError");
|
||||
let message = getTranslationID("common.message.internalServerError");
|
||||
if (error.code === "E010204") {
|
||||
message = getTranslationID(
|
||||
"typistGroupSetting.message.groupSaveFailedError"
|
||||
);
|
||||
}
|
||||
if (error.code === "E010909") {
|
||||
message = getTranslationID(
|
||||
"typistGroupSetting.message.GroupNameAlreadyExistError"
|
||||
);
|
||||
}
|
||||
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
@ -242,10 +248,17 @@ export const updateTypistGroupAsync = createAsyncThunk<
|
||||
// e ⇒ errorObjectに変換"
|
||||
const error = createErrorObject(e);
|
||||
|
||||
const message =
|
||||
error.statusCode === 400
|
||||
? getTranslationID("typistGroupSetting.message.groupSaveFailedError")
|
||||
: getTranslationID("common.message.internalServerError");
|
||||
let message = getTranslationID("common.message.internalServerError");
|
||||
if (error.code === "E010204" || error.code === "E010908") {
|
||||
message = getTranslationID(
|
||||
"typistGroupSetting.message.groupSaveFailedError"
|
||||
);
|
||||
}
|
||||
if (error.code === "E010909") {
|
||||
message = getTranslationID(
|
||||
"typistGroupSetting.message.GroupNameAlreadyExistError"
|
||||
);
|
||||
}
|
||||
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
|
||||
@ -191,23 +191,32 @@ const AccountPage: React.FC = (): JSX.Element => {
|
||||
)}
|
||||
</dt>
|
||||
{isTier5 && (
|
||||
<dd>
|
||||
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
className={styles.formCheck}
|
||||
checked={updateAccountInfo.delegationPermission}
|
||||
onChange={(e) => {
|
||||
dispatch(
|
||||
changeDealerPermission({
|
||||
delegationPermission: e.target.checked,
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
</dd>
|
||||
<>
|
||||
<dd>
|
||||
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
className={styles.formCheck}
|
||||
checked={updateAccountInfo.delegationPermission}
|
||||
onChange={(e) => {
|
||||
dispatch(
|
||||
changeDealerPermission({
|
||||
delegationPermission: e.target.checked,
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
</dd>
|
||||
<dd className={`${styles.full} ${styles.formComment}`}>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"accountPage.text.dealerManagementAnnotation"
|
||||
)
|
||||
)}
|
||||
</dd>
|
||||
</>
|
||||
)}
|
||||
{!isTier5 && <dd>-</dd>}
|
||||
</dl>
|
||||
@ -374,6 +383,15 @@ const AccountPage: React.FC = (): JSX.Element => {
|
||||
className={styles.icLoading}
|
||||
alt="Loading"
|
||||
/>
|
||||
{isTier5 && (
|
||||
<p className={styles.formComment}>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"accountPage.text.dealerManagementAnnotation"
|
||||
)
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{isTier5 && (
|
||||
|
||||
@ -33,6 +33,8 @@ import {
|
||||
playbackAsync,
|
||||
cancelAsync,
|
||||
PRIORITY,
|
||||
isSortableColumnType,
|
||||
isDirectionType,
|
||||
} from "features/dictation";
|
||||
import { getTranslationID } from "translation";
|
||||
import { Task } from "api/api";
|
||||
@ -242,6 +244,12 @@ const DictationPage: React.FC = (): JSX.Element => {
|
||||
dispatch(changeDirection({ direction: currentDirection }));
|
||||
dispatch(changeParamName({ paramName }));
|
||||
|
||||
// ローカルストレージにソート情報を保存する
|
||||
localStorage.setItem(
|
||||
"sortCriteria",
|
||||
`direction:${currentDirection},paramName:${paramName}`
|
||||
);
|
||||
|
||||
const filter = getFilter(
|
||||
filterUploaded,
|
||||
filterInProgress,
|
||||
@ -348,10 +356,11 @@ const DictationPage: React.FC = (): JSX.Element => {
|
||||
audioFileId,
|
||||
direction: sortDirection,
|
||||
paramName: sortableParamName,
|
||||
isTypist,
|
||||
})
|
||||
);
|
||||
if (meta.requestStatus === "fulfilled") {
|
||||
// ローカルストレージにソート情報を削除する
|
||||
localStorage.removeItem("sortCriteria");
|
||||
const filter = getFilter(
|
||||
filterUploaded,
|
||||
filterInProgress,
|
||||
@ -388,7 +397,6 @@ const DictationPage: React.FC = (): JSX.Element => {
|
||||
filterInProgress,
|
||||
filterPending,
|
||||
filterUploaded,
|
||||
isTypist,
|
||||
sortDirection,
|
||||
sortableParamName,
|
||||
t,
|
||||
@ -522,13 +530,39 @@ const DictationPage: React.FC = (): JSX.Element => {
|
||||
dispatch(changeDisplayInfo({ column: displayInfo }));
|
||||
|
||||
const filter = getFilter(true, true, true, true, false);
|
||||
|
||||
const { meta, payload } = await dispatch(getSortColumnAsync());
|
||||
if (
|
||||
meta.requestStatus === "fulfilled" &&
|
||||
payload &&
|
||||
!("error" in payload)
|
||||
) {
|
||||
const { direction, paramName } = payload;
|
||||
// ソート情報をローカルストレージから取得する
|
||||
const sortColumnValue = localStorage.getItem("sortCriteria") ?? "";
|
||||
let direction: DirectionType;
|
||||
let paramName: SortableColumnType;
|
||||
if (sortColumnValue === "") {
|
||||
direction = payload.direction;
|
||||
paramName = payload.paramName;
|
||||
} else {
|
||||
// ソート情報をDirectionとParamNameに分割する
|
||||
const sortColumn = sortColumnValue?.split(",");
|
||||
const localStorageDirection = sortColumn[0].split(":")[1] ?? "";
|
||||
|
||||
const localStorageParamName = sortColumn[1]?.split(":")[1] ?? "";
|
||||
|
||||
// 正常なソート情報がローカルストレージに存在する場合はローカルストレージの情報を使用する
|
||||
direction = isDirectionType(localStorageDirection)
|
||||
? localStorageDirection
|
||||
: payload.direction;
|
||||
paramName = isSortableColumnType(localStorageParamName)
|
||||
? localStorageParamName
|
||||
: payload.paramName;
|
||||
|
||||
dispatch(changeDirection({ direction }));
|
||||
dispatch(changeParamName({ paramName }));
|
||||
}
|
||||
|
||||
dispatch(
|
||||
listTasksAsync({
|
||||
limit: LIMIT_TASK_NUM,
|
||||
@ -1082,7 +1116,13 @@ const DictationPage: React.FC = (): JSX.Element => {
|
||||
{(isChangeTranscriptionistPopupOpen || !isLoading) &&
|
||||
tasks.length !== 0 &&
|
||||
tasks.map((x) => (
|
||||
<tr key={x.audioFileId}>
|
||||
<tr
|
||||
key={x.audioFileId}
|
||||
style={{
|
||||
backgroundColor:
|
||||
x.priority === "01" ? "#ff00004f" : "#ffffff",
|
||||
}}
|
||||
>
|
||||
<td className={styles.clm0}>
|
||||
<ul className={styles.menuInTable}>
|
||||
<li>
|
||||
|
||||
@ -85,7 +85,7 @@ export const AddWorktypeIdPopup: React.FC<AddWorktypeIdPopupProps> = (
|
||||
<input
|
||||
type="text"
|
||||
size={40}
|
||||
maxLength={255}
|
||||
maxLength={16}
|
||||
value={worktypeId ?? ""}
|
||||
className={styles.formInput}
|
||||
onChange={(e) => {
|
||||
|
||||
@ -84,7 +84,7 @@ export const EditWorktypeIdPopup: React.FC<EditWorktypeIdPopupProps> = (
|
||||
<input
|
||||
type="text"
|
||||
size={40}
|
||||
maxLength={255}
|
||||
maxLength={16}
|
||||
value={worktypeId ?? ""}
|
||||
className={styles.formInput}
|
||||
onChange={(e) => {
|
||||
|
||||
@ -1343,23 +1343,23 @@ _:-ms-lang(x)::-ms-backdrop,
|
||||
.tableHeader th .hasSort:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
.tableHeader th .hasSort.isActiveAz::before {
|
||||
opacity: 1;
|
||||
}
|
||||
.tableHeader th .hasSort.isActiveAz:hover::before {
|
||||
border-top: none;
|
||||
border-right: 0.35rem transparent solid;
|
||||
border-bottom: 0.4rem #ffffff solid;
|
||||
border-left: 0.35rem transparent solid;
|
||||
}
|
||||
.tableHeader th .hasSort.isActiveZa::before {
|
||||
border-top: none;
|
||||
border-right: 0.35rem transparent solid;
|
||||
border-bottom: 0.4rem #ffffff solid;
|
||||
border-left: 0.35rem transparent solid;
|
||||
opacity: 1;
|
||||
}
|
||||
.tableHeader th .hasSort.isActiveZa:hover::before {
|
||||
border-top: none;
|
||||
border-right: 0.35rem transparent solid;
|
||||
border-bottom: 0.4rem #ffffff solid;
|
||||
border-left: 0.35rem transparent solid;
|
||||
}
|
||||
.tableHeader th .hasSort.isActiveAz::before {
|
||||
border-top: none;
|
||||
border-right: 0.35rem transparent solid;
|
||||
border-bottom: 0.4rem #ffffff solid;
|
||||
border-left: 0.35rem transparent solid;
|
||||
opacity: 1;
|
||||
}
|
||||
.tableHeader th .hasSort.isActiveAz:hover::before {
|
||||
border-top: 0.4rem #ffffff solid;
|
||||
border-right: 0.35rem transparent solid;
|
||||
border-bottom: none;
|
||||
@ -1632,8 +1632,31 @@ _:-ms-lang(x)::-ms-backdrop,
|
||||
.account .listVertical dd .formInput {
|
||||
max-width: 100%;
|
||||
}
|
||||
.account .listVertical dd.full {
|
||||
width: 100%;
|
||||
padding-top: 0;
|
||||
background: none;
|
||||
}
|
||||
.account .listVertical dd.full.odd {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.account .listVertical dd.formComment {
|
||||
text-align: left;
|
||||
font-size: 0.9rem;
|
||||
word-break: break-word;
|
||||
}
|
||||
.account .box100 .formComment {
|
||||
display: block;
|
||||
width: 600px;
|
||||
text-align: left;
|
||||
}
|
||||
.account .box100.alignRight {
|
||||
width: calc(1200px + 3rem);
|
||||
text-align: right;
|
||||
}
|
||||
.account .box100.alignRight .formComment {
|
||||
margin-left: 648px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.menuAction {
|
||||
@ -2306,7 +2329,8 @@ tr.isSelected .menuInTable li a.isDisable {
|
||||
}
|
||||
.formChange ul.chooseMember li input + label:hover,
|
||||
.formChange ul.holdMember li input + label:hover {
|
||||
background: #e6e6e6 url(../assets/images/arrow_circle_left.svg) no-repeat left center;
|
||||
background: #e6e6e6 url(../assets/images/arrow_circle_left.svg) no-repeat left
|
||||
center;
|
||||
background-size: 1.3rem;
|
||||
}
|
||||
.formChange ul.chooseMember li input:checked + label,
|
||||
@ -2317,8 +2341,8 @@ tr.isSelected .menuInTable li a.isDisable {
|
||||
}
|
||||
.formChange ul.chooseMember li input:checked + label:hover,
|
||||
.formChange ul.holdMember li input:checked + label:hover {
|
||||
background: #e6e6e6 url(../assets/images/arrow_circle_right.svg) no-repeat right
|
||||
center;
|
||||
background: #e6e6e6 url(../assets/images/arrow_circle_right.svg) no-repeat
|
||||
right center;
|
||||
background-size: 1.3rem;
|
||||
}
|
||||
.formChange > p {
|
||||
@ -2471,7 +2495,8 @@ tr.isSelected .menuInTable li a.isDisable {
|
||||
}
|
||||
.formChange ul.chooseMember li input + label:hover,
|
||||
.formChange ul.holdMember li input + label:hover {
|
||||
background: #e6e6e6 url(../assets/images/arrow_circle_left.svg) no-repeat left center;
|
||||
background: #e6e6e6 url(../assets/images/arrow_circle_left.svg) no-repeat left
|
||||
center;
|
||||
background-size: 1.3rem;
|
||||
}
|
||||
.formChange ul.chooseMember li input:checked + label,
|
||||
@ -2482,8 +2507,8 @@ tr.isSelected .menuInTable li a.isDisable {
|
||||
}
|
||||
.formChange ul.chooseMember li input:checked + label:hover,
|
||||
.formChange ul.holdMember li input:checked + label:hover {
|
||||
background: #e6e6e6 url(../assets/images/arrow_circle_right.svg) no-repeat right
|
||||
center;
|
||||
background: #e6e6e6 url(../assets/images/arrow_circle_right.svg) no-repeat
|
||||
right center;
|
||||
background-size: 1.3rem;
|
||||
}
|
||||
.formChange > p {
|
||||
|
||||
@ -89,8 +89,8 @@ declare const classNames: {
|
||||
readonly snackbarIcon: "snackbarIcon";
|
||||
readonly snackbarIconClose: "snackbarIconClose";
|
||||
readonly hasSort: "hasSort";
|
||||
readonly isActiveAz: "isActiveAz";
|
||||
readonly isActiveZa: "isActiveZa";
|
||||
readonly isActiveAz: "isActiveAz";
|
||||
readonly noLine: "noLine";
|
||||
readonly home: "home";
|
||||
readonly pgHome: "pgHome";
|
||||
@ -107,6 +107,7 @@ declare const classNames: {
|
||||
readonly clm0: "clm0";
|
||||
readonly menuInTable: "menuInTable";
|
||||
readonly isSelected: "isSelected";
|
||||
readonly odd: "odd";
|
||||
readonly alignRight: "alignRight";
|
||||
readonly menuAction: "menuAction";
|
||||
readonly inTable: "inTable";
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
"headerDictations": "Diktate",
|
||||
"headerWorkflow": "Arbeitsablauf",
|
||||
"headerPartners": "Partner",
|
||||
"headerSupport": "(de)Support",
|
||||
"headerSupport": "Support",
|
||||
"tier1": "Admin",
|
||||
"tier2": "BC",
|
||||
"tier3": "Verteiler",
|
||||
@ -76,6 +76,7 @@
|
||||
"linkOfEula": "Klicken Sie hier, um die Endbenutzer-Lizenzvereinbarung zu lesen.",
|
||||
"linkOfPrivacyNotice": "Klicken Sie hier, um die Datenschutzerklärung zu lesen.",
|
||||
"forOdms": "für ODMS Cloud.",
|
||||
"termsCheckBox": "Ja, ich stimme den Nutzungsbedingungen zu.",
|
||||
"createAccountButton": "Einreichen"
|
||||
}
|
||||
},
|
||||
@ -127,7 +128,14 @@
|
||||
"authorIdIncorrectError": "Das Format der Autoren-ID ist ungültig. Als Autoren-ID können nur alphanumerische Zeichen und „_“ eingegeben werden.",
|
||||
"roleChangeError": "Die Benutzerrolle kann nicht geändert werden. Die angezeigten Informationen sind möglicherweise veraltet. Aktualisieren Sie daher bitte den Bildschirm, um den neuesten Status anzuzeigen.",
|
||||
"encryptionPasswordCorrectError": "Das Verschlüsselungskennwort entspricht nicht den Regeln.",
|
||||
"alreadyLicenseDeallocatedError": "Die zugewiesene Lizenz wurde bereits storniert. Die angezeigten Informationen sind möglicherweise veraltet. Aktualisieren Sie daher bitte den Bildschirm, um den neuesten Status anzuzeigen."
|
||||
"alreadyLicenseDeallocatedError": "Die zugewiesene Lizenz wurde bereits storniert. Die angezeigten Informationen sind möglicherweise veraltet. Aktualisieren Sie daher bitte den Bildschirm, um den neuesten Status anzuzeigen.",
|
||||
"UserDeletionLicenseActiveError": "(de)ユーザーの削除に失敗しました。対象ユーザーのライセンス割り当てを解除してください。",
|
||||
"TypistDeletionRoutingRuleError": "(de)ユーザーの削除に失敗しました。Workflow画面でルーティングルールから対象Transcriptionistを外してください。",
|
||||
"AdminUserDeletionError": "(de)ユーザーの削除に失敗しました。アカウント画面で対象ユーザーをPrimary/Secondary Administratorから外してください。",
|
||||
"TypistUserDeletionTranscriptionTaskError": "(de)ユーザーの削除に失敗しました。Dictation画面でタスクのルーティングから対象Transcriptionistを外してください。",
|
||||
"AuthorUserDeletionTranscriptionTaskError": "(de)ユーザーの削除に失敗しました。Dictation画面で対象AuthorのAuthorIDが設定されているタスクの中で、文字起こしが未完了のタスクを削除またはFinishedにしてください。",
|
||||
"TypistUserDeletionTranscriptionistGroupError": "(de)ユーザーの削除に失敗しました。Workflow画面でTranscriptionistGroupから対象Transcriptionistを外してください。",
|
||||
"AuthorDeletionRoutingRuleError": "(de)ユーザーの削除に失敗しました。Workflow画面でルーティングルールから対象AuthorのAuthorIDを外してください。"
|
||||
},
|
||||
"label": {
|
||||
"title": "Benutzer",
|
||||
@ -179,8 +187,8 @@
|
||||
"storageSize": "Lagerung verfügbar",
|
||||
"usedSize": "Gebrauchter Lagerung",
|
||||
"storageAvailable": "Speicher nicht verfügbar (Menge überschritten)",
|
||||
"licenseLabel": "(de)License",
|
||||
"storageLabel": "(de)Storage"
|
||||
"licenseLabel": "Lizenz",
|
||||
"storageLabel": "Lagerung"
|
||||
}
|
||||
},
|
||||
"licenseOrderPage": {
|
||||
@ -189,7 +197,8 @@
|
||||
"poNumberIncorrectError": "Das Format der Bestellnummer ist ungültig. Für die Bestellnummer können nur alphanumerische Zeichen eingegeben werden.",
|
||||
"newOrderIncorrectError": "Bitte geben Sie für die neue Bestellung eine Zahl größer oder gleich 1 ein.",
|
||||
"confirmOrder": "Möchten Sie eine Bestellung aufgeben?",
|
||||
"poNumberConflictError": "Die eingegebene Bestellnummer existiert bereits. Bitte geben Sie eine andere Bestellnummer ein."
|
||||
"poNumberConflictError": "Die eingegebene Bestellnummer existiert bereits. Bitte geben Sie eine andere Bestellnummer ein.",
|
||||
"dealerNotFoundError": "(de)ディーラーが設定されていないため、ライセンスを注文できません。アカウント画面でディーラーを指定してください。"
|
||||
},
|
||||
"label": {
|
||||
"title": "Lizenz bestellen",
|
||||
@ -205,8 +214,9 @@
|
||||
"noPlaybackAuthorization": "Sie haben keine Berechtigung zum Abspielen dieser Datei.",
|
||||
"taskToPlaybackNoExists": "Die Datei kann nicht abgespielt werden, da sie bereits transkribiert wurde oder nicht existiert.",
|
||||
"taskNotEditable": "Der Transkriptionist kann nicht geändert werden, da die Transkription bereits ausgeführt wird oder die Datei nicht vorhanden ist. Bitte aktualisieren Sie den Bildschirm und prüfen Sie den aktuellen Status.",
|
||||
"backupFailedError": "(de)ファイルのバックアップに失敗したため処理を中断しました。時間をおいて再実行しても解決しない場合はシステム管理者にお問い合わせください。",
|
||||
"cancelFailedError": "(de)タスクのキャンセルに失敗しました。画面を更新し、再度ご確認ください。"
|
||||
"backupFailedError": "Der Prozess „Dateisicherung“ ist fehlgeschlagen. Bitte versuchen Sie es später noch einmal. Wenn der Fehler weiterhin besteht, wenden Sie sich an Ihren Systemadministrator.",
|
||||
"cancelFailedError": "Die Diktate konnten nicht gelöscht werden. Bitte aktualisieren Sie Ihren Bildschirm und versuchen Sie es erneut.",
|
||||
"deleteFailedError": "(de)タスクの削除に失敗しました。画面を更新し、再度ご確認ください。"
|
||||
},
|
||||
"label": {
|
||||
"title": "Diktate",
|
||||
@ -249,9 +259,9 @@
|
||||
"deleteDictation": "Diktat löschen",
|
||||
"selectedTranscriptionist": "Ausgewählter transkriptionist",
|
||||
"poolTranscriptionist": "Transkriptionsliste",
|
||||
"fileBackup": "(de)File Backup",
|
||||
"downloadForBackup": "(de)Download for backup",
|
||||
"applications": "(de)Applications",
|
||||
"fileBackup": "Dateisicherung",
|
||||
"downloadForBackup": "Zur Sicherung herunterladen",
|
||||
"applications": "Desktopanwendung",
|
||||
"cancelDictation": "Transkription abbrechen"
|
||||
}
|
||||
},
|
||||
@ -414,7 +424,10 @@
|
||||
},
|
||||
"message": {
|
||||
"selectedTypistEmptyError": "Um eine Transkriptionsgruppe zu speichern, müssen ein oder mehrere Transkriptionisten ausgewählt werden.",
|
||||
"groupSaveFailedError": "Die Schreibkraftgruppe konnte nicht gespeichert werden. Die angezeigten Informationen sind möglicherweise veraltet. Aktualisieren Sie daher bitte den Bildschirm, um den neuesten Status anzuzeigen."
|
||||
"groupSaveFailedError": "Die Transkriptionistengruppe konnte nicht gespeichert werden. Die angezeigten Informationen sind möglicherweise veraltet. Aktualisieren Sie daher bitte den Bildschirm, um den neuesten Status anzuzeigen.",
|
||||
"GroupNameAlreadyExistError": "(de)このTranscriptionistGroup名は既に登録されています。他のTranscriptionistGroup名で登録してください。",
|
||||
"deleteFailedWorkflowAssigned": "(de)TranscriptionistGroupの削除に失敗しました。Workflow画面でルーティングルールから対象TranscriptionistGroupを外してください。",
|
||||
"deleteFailedCheckoutPermissionExisted": "(de)TranscriptionistGroupの削除に失敗しました。Dictation画面でタスクのルーティングから対象TranscriptionistGroupを外してください。"
|
||||
}
|
||||
},
|
||||
"worktypeIdSetting": {
|
||||
@ -470,7 +483,7 @@
|
||||
"addAccount": "Konto hinzufügen",
|
||||
"name": "Name der Firma",
|
||||
"category": "Kontoebene",
|
||||
"accountId": "Konto-ID",
|
||||
"accountId": "Autoren-ID",
|
||||
"country": "Land",
|
||||
"primaryAdmin": "Hauptadministrator",
|
||||
"email": "Email",
|
||||
@ -506,6 +519,9 @@
|
||||
},
|
||||
"message": {
|
||||
"updateAccountFailedError": "Kontoinformationen konnten nicht gespeichert werden. Bitte aktualisieren Sie den Bildschirm und versuchen Sie es erneut."
|
||||
},
|
||||
"text": {
|
||||
"dealerManagementAnnotation": "Durch die Aktivierung der Option „Erlauben Sie dem Händler, Änderungen vorzunehmen“ erklären Sie sich damit einverstanden, dass Ihr Händler die Rechte erhält, auf Ihr ODMS Cloud-Konto zuzugreifen, um in Ihrem Namen Lizenzen zu bestellen und Benutzer zu registrieren. Ihr Händler hat keinen Zugriff auf Sprachdateien oder Dokumente, die in Ihrem ODMS Cloud-Konto gespeichert sind."
|
||||
}
|
||||
},
|
||||
"deleteAccountPopup": {
|
||||
@ -537,22 +553,22 @@
|
||||
},
|
||||
"supportPage": {
|
||||
"label": {
|
||||
"title": "(de)Support",
|
||||
"howToUse": "(de)How to use the system",
|
||||
"title": "Support",
|
||||
"howToUse": "So verwenden Sie das System",
|
||||
"supportPageEnglish": "OMDS Cloud User Guide",
|
||||
"supportPageGerman": "OMDS Cloud Benutzerhandbuch",
|
||||
"supportPageFrench": "OMDS Cloud Mode d'emploi",
|
||||
"supportPageSpanish": "OMDS Cloud Guía del usuario"
|
||||
"supportPageGerman": "OMDS Cloud-Benutzerhandbuch",
|
||||
"supportPageFrench": "Guía del usuario de la nube OMDS",
|
||||
"supportPageSpanish": "Guide de l'utilisateur du cloud OMDS"
|
||||
},
|
||||
"text": {
|
||||
"notResolved": "(de)If the problem persists even after referring to the user guide, please contact a higher-level person in charge."
|
||||
"notResolved": "Informationen zu den Funktionen der ODMS Cloud finden Sie im Benutzerhandbuch. Wenn Sie zusätzlichen Support benötigen, wenden Sie sich bitte an Ihren Administrator oder zertifizierten ODMS Cloud-Händler."
|
||||
}
|
||||
},
|
||||
"filePropertyPopup": {
|
||||
"label": {
|
||||
"general": "(de)General",
|
||||
"job": "(de)Job",
|
||||
"close": "(de)Close"
|
||||
"general": "Allgemein",
|
||||
"job": "Aufgabe",
|
||||
"close": "Schließen"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,7 +75,7 @@
|
||||
"password": "Password",
|
||||
"linkOfEula": "Click here to read the End User License Agreement.",
|
||||
"linkOfPrivacyNotice": "Click here to read the Privacy Notice.",
|
||||
"forOdms": "for ODMS Cloud.",
|
||||
"forOdms": "for OMDS Cloud.",
|
||||
"termsCheckBox": "Yes, I agree to the terms of use.",
|
||||
"createAccountButton": "Submit"
|
||||
}
|
||||
@ -128,7 +128,14 @@
|
||||
"authorIdIncorrectError": "Author ID format is invalid. Only alphanumeric characters and \"_\" can be entered for Author ID.",
|
||||
"roleChangeError": "Unable to change the User Role. The displayed information may be outdated, so please refresh the screen to see the latest status.",
|
||||
"encryptionPasswordCorrectError": "Encryption password does not meet the rules.",
|
||||
"alreadyLicenseDeallocatedError": "Assigned license has already been canceled. The displayed information may be outdated, so please refresh the screen to see the latest status."
|
||||
"alreadyLicenseDeallocatedError": "Assigned license has already been canceled. The displayed information may be outdated, so please refresh the screen to see the latest status.",
|
||||
"UserDeletionLicenseActiveError": "ユーザーの削除に失敗しました。対象ユーザーのライセンス割り当てを解除してください。",
|
||||
"TypistDeletionRoutingRuleError": "ユーザーの削除に失敗しました。Workflow画面でルーティングルールから対象Transcriptionistを外してください。",
|
||||
"AdminUserDeletionError": "ユーザーの削除に失敗しました。アカウント画面で対象ユーザーをPrimary/Secondary Administratorから外してください。",
|
||||
"TypistUserDeletionTranscriptionTaskError": "ユーザーの削除に失敗しました。Dictation画面でタスクのルーティングから対象Transcriptionistを外してください。",
|
||||
"AuthorUserDeletionTranscriptionTaskError": "ユーザーの削除に失敗しました。Dictation画面で対象AuthorのAuthorIDが設定されているタスクの中で、文字起こしが未完了のタスクを削除またはFinishedにしてください。",
|
||||
"TypistUserDeletionTranscriptionistGroupError": "ユーザーの削除に失敗しました。Workflow画面でTranscriptionistGroupから対象Transcriptionistを外してください。",
|
||||
"AuthorDeletionRoutingRuleError": "ユーザーの削除に失敗しました。Workflow画面でルーティングルールから対象AuthorのAuthorIDを外してください。"
|
||||
},
|
||||
"label": {
|
||||
"title": "User",
|
||||
@ -190,7 +197,8 @@
|
||||
"poNumberIncorrectError": "PO Number format is not valid. Only alphanumeric characters can be entered for the PO Number.",
|
||||
"newOrderIncorrectError": "Please enter a number greater than or equal to 1 for the New Order.",
|
||||
"confirmOrder": "Would you like to place an order?",
|
||||
"poNumberConflictError": "PO Number entered already exists. Please enter a different PO Number."
|
||||
"poNumberConflictError": "PO Number entered already exists. Please enter a different PO Number.",
|
||||
"dealerNotFoundError": "ディーラーが設定されていないため、ライセンスを注文できません。アカウント画面でディーラーを指定してください。"
|
||||
},
|
||||
"label": {
|
||||
"title": "Order License",
|
||||
@ -206,8 +214,9 @@
|
||||
"noPlaybackAuthorization": "You do not have permission to playback this file.",
|
||||
"taskToPlaybackNoExists": "The file cannot be played because it has already been transcribed or does not exist.",
|
||||
"taskNotEditable": "The transcriptionist cannot be changed because the transcription is already in progress or the file does not exist. Please refresh the screen and check the latest status.",
|
||||
"backupFailedError": "ファイルのバックアップに失敗したため処理を中断しました。時間をおいて再実行しても解決しない場合はシステム管理者にお問い合わせください。",
|
||||
"cancelFailedError": "タスクのキャンセルに失敗しました。画面を更新し、再度ご確認ください。"
|
||||
"backupFailedError": "The \"File Backup\" process has failed. Please try again later. If the error continues, contact your system administrator.",
|
||||
"cancelFailedError": "Failed to delete the dictations. Please refresh your screen and try again.",
|
||||
"deleteFailedError": "タスクの削除に失敗しました。画面を更新し、再度ご確認ください。"
|
||||
},
|
||||
"label": {
|
||||
"title": "Dictations",
|
||||
@ -252,7 +261,7 @@
|
||||
"poolTranscriptionist": "Transcription List",
|
||||
"fileBackup": "File Backup",
|
||||
"downloadForBackup": "Download for backup",
|
||||
"applications": "Applications",
|
||||
"applications": "Desktop Application",
|
||||
"cancelDictation": "Cancel Transcription"
|
||||
}
|
||||
},
|
||||
@ -415,7 +424,10 @@
|
||||
},
|
||||
"message": {
|
||||
"selectedTypistEmptyError": "One or more transcriptonist must be selected to save a transcrption group.",
|
||||
"groupSaveFailedError": "Typist Group could not be saved. The displayed information may be outdated, so please refresh the screen to see the latest status."
|
||||
"groupSaveFailedError": "Transcriptionist Group could not be saved. The displayed information may be outdated, so please refresh the screen to see the latest status.",
|
||||
"GroupNameAlreadyExistError": "このTranscriptionistGroup名は既に登録されています。他のTranscriptionistGroup名で登録してください。",
|
||||
"deleteFailedWorkflowAssigned": "TranscriptionistGroupの削除に失敗しました。Workflow画面でルーティングルールから対象TranscriptionistGroupを外してください。",
|
||||
"deleteFailedCheckoutPermissionExisted": "TranscriptionistGroupの削除に失敗しました。Dictation画面でタスクのルーティングから対象TranscriptionistGroupを外してください。"
|
||||
}
|
||||
},
|
||||
"worktypeIdSetting": {
|
||||
@ -507,6 +519,9 @@
|
||||
},
|
||||
"message": {
|
||||
"updateAccountFailedError": "Failed to save account information. Please refresh the screen and try again."
|
||||
},
|
||||
"text": {
|
||||
"dealerManagementAnnotation": "By enabling the \"Dealer Management\" option, you are agreeing to allow your dealer to have the rights to access your ODMS Cloud account to order licenses and register users on your behalf. Your dealer will not have access to any voice file(s) or document(s) stored in your ODMS Cloud account."
|
||||
}
|
||||
},
|
||||
"deleteAccountPopup": {
|
||||
@ -541,12 +556,12 @@
|
||||
"title": "Support",
|
||||
"howToUse": "How to use the system",
|
||||
"supportPageEnglish": "OMDS Cloud User Guide",
|
||||
"supportPageGerman": "OMDS Cloud Benutzerhandbuch",
|
||||
"supportPageFrench": "OMDS Cloud Mode d'emploi",
|
||||
"supportPageSpanish": "OMDS Cloud Guía del usuario"
|
||||
"supportPageGerman": "OMDS Cloud-Benutzerhandbuch",
|
||||
"supportPageFrench": "Guía del usuario de la nube OMDS",
|
||||
"supportPageSpanish": "Guide de l'utilisateur du cloud OMDS"
|
||||
},
|
||||
"text": {
|
||||
"notResolved": "If the problem persists even after referring to the user guide, please contact a higher-level person in charge."
|
||||
"notResolved": "Please refer to the User Guide for information about the features of the ODMS Cloud. If you require additional support, please contact your administrator or certified ODMS Cloud reseller."
|
||||
}
|
||||
},
|
||||
"filePropertyPopup": {
|
||||
|
||||
@ -24,11 +24,11 @@
|
||||
"headerDictations": "Dictado",
|
||||
"headerWorkflow": "flujo de trabajo",
|
||||
"headerPartners": "Socios",
|
||||
"headerSupport": "(es)Support",
|
||||
"headerSupport": "Soporte",
|
||||
"tier1": "Admin",
|
||||
"tier2": "BC",
|
||||
"tier3": "Distribuidor",
|
||||
"tier4": "Concesionario",
|
||||
"tier4": "Distribuidor",
|
||||
"tier5": "Cliente",
|
||||
"notSelected": "Ninguno",
|
||||
"signOutButton": "cerrar sesión"
|
||||
@ -62,14 +62,14 @@
|
||||
"title": "Crea tu cuenta",
|
||||
"accountInfoTitle": "Información de Registro",
|
||||
"countryExplanation": "Seleccione el país donde se encuentra. Si su país no aparece en la lista, seleccione el país más cercano.",
|
||||
"dealerExplanation": "Seleccione el concesionario al que le gustaría comprar la licencia.",
|
||||
"dealerExplanation": "Seleccione el distribuidor al que le gustaría comprar la licencia.",
|
||||
"adminInfoTitle": "Registre la información del administrador principal",
|
||||
"passwordTerms": "Establezca una contraseña. La contraseña debe tener entre 8 y 25 caracteres y debe contener letras, números y símbolos. (Debe enumerar el símbolo compatible e indicar si se necesita una letra mayúscula)."
|
||||
},
|
||||
"label": {
|
||||
"company": "Nombre de empresa",
|
||||
"country": "País",
|
||||
"dealer": "Concesionario (Opcional)",
|
||||
"dealer": "Distribuidor (Opcional)",
|
||||
"adminName": "Nombre del administrador",
|
||||
"email": "Dirección de correo electrónico",
|
||||
"password": "Contraseña",
|
||||
@ -93,7 +93,7 @@
|
||||
"label": {
|
||||
"company": "Nombre de empresa",
|
||||
"country": "País",
|
||||
"dealer": "Concesionario (Opcional)",
|
||||
"dealer": "Distribuidor (Opcional)",
|
||||
"adminName": "Nombre del administrador",
|
||||
"email": "Dirección de correo electrónico",
|
||||
"password": "Contraseña",
|
||||
@ -128,7 +128,14 @@
|
||||
"authorIdIncorrectError": "El formato de ID del autor no es válido. Sólo se pueden ingresar caracteres alfanuméricos y \"_\" para la ID del autor.",
|
||||
"roleChangeError": "No se puede cambiar la función de usuario. La información mostrada puede estar desactualizada, así que actualice la pantalla para ver el estado más reciente.",
|
||||
"encryptionPasswordCorrectError": "La contraseña de cifrado no cumple con las reglas.",
|
||||
"alreadyLicenseDeallocatedError": "La licencia asignada ya ha sido cancelada. La información mostrada puede estar desactualizada, así que actualice la pantalla para ver el estado más reciente."
|
||||
"alreadyLicenseDeallocatedError": "La licencia asignada ya ha sido cancelada. La información mostrada puede estar desactualizada, así que actualice la pantalla para ver el estado más reciente.",
|
||||
"UserDeletionLicenseActiveError": "(es)ユーザーの削除に失敗しました。対象ユーザーのライセンス割り当てを解除してください。",
|
||||
"TypistDeletionRoutingRuleError": "(es)ユーザーの削除に失敗しました。Workflow画面でルーティングルールから対象Transcriptionistを外してください。",
|
||||
"AdminUserDeletionError": "(es)ユーザーの削除に失敗しました。アカウント画面で対象ユーザーをPrimary/Secondary Administratorから外してください。",
|
||||
"TypistUserDeletionTranscriptionTaskError": "(es)ユーザーの削除に失敗しました。Dictation画面でタスクのルーティングから対象Transcriptionistを外してください。",
|
||||
"AuthorUserDeletionTranscriptionTaskError": "(es)ユーザーの削除に失敗しました。Dictation画面で対象AuthorのAuthorIDが設定されているタスクの中で、文字起こしが未完了のタスクを削除またはFinishedにしてください。",
|
||||
"TypistUserDeletionTranscriptionistGroupError": "(es)ユーザーの削除に失敗しました。Workflow画面でTranscriptionistGroupから対象Transcriptionistを外してください。",
|
||||
"AuthorDeletionRoutingRuleError": "(es)ユーザーの削除に失敗しました。Workflow画面でルーティングルールから対象AuthorのAuthorIDを外してください。"
|
||||
},
|
||||
"label": {
|
||||
"title": "Usuario",
|
||||
@ -180,8 +187,8 @@
|
||||
"storageSize": "Almacenamiento disponible",
|
||||
"usedSize": "Almacenamiento utilizado",
|
||||
"storageAvailable": "Almacenamiento no disponible (cantidad excedida)",
|
||||
"licenseLabel": "(es)License",
|
||||
"storageLabel": "(es)Storage"
|
||||
"licenseLabel": "Licencia",
|
||||
"storageLabel": "Almacenamiento"
|
||||
}
|
||||
},
|
||||
"licenseOrderPage": {
|
||||
@ -190,7 +197,8 @@
|
||||
"poNumberIncorrectError": "El formato del número de orden de compra no es válido. Sólo se pueden ingresar caracteres alfanuméricos para el número de orden de compra.",
|
||||
"newOrderIncorrectError": "Ingrese un número mayor o igual a 1 para el Nuevo Pedido.",
|
||||
"confirmOrder": "¿Quieres hacer un pedido?",
|
||||
"poNumberConflictError": "El número de orden de compra ingresado ya existe. Ingrese un número de orden de compra diferente."
|
||||
"poNumberConflictError": "El número de orden de compra ingresado ya existe. Ingrese un número de orden de compra diferente.",
|
||||
"dealerNotFoundError": "(es)ディーラーが設定されていないため、ライセンスを注文できません。アカウント画面でディーラーを指定してください。"
|
||||
},
|
||||
"label": {
|
||||
"title": "Licencia de pedido",
|
||||
@ -206,8 +214,9 @@
|
||||
"noPlaybackAuthorization": "No tienes permiso para reproducir este archivo.",
|
||||
"taskToPlaybackNoExists": "El archivo no se puede reproducir porque ya ha sido transcrito o no existe.",
|
||||
"taskNotEditable": "No se puede cambiar el transcriptor porque la transcripción ya está en curso o el archivo no existe. Actualice la pantalla y verifique el estado más reciente.",
|
||||
"backupFailedError": "(es)ファイルのバックアップに失敗したため処理を中断しました。時間をおいて再実行しても解決しない場合はシステム管理者にお問い合わせください。",
|
||||
"cancelFailedError": "(es)タスクのキャンセルに失敗しました。画面を更新し、再度ご確認ください。"
|
||||
"backupFailedError": "El proceso de \"Copia de seguridad de archivos\" ha fallado. Por favor, inténtelo de nuevo más tarde. Si el error continúa, comuníquese con el administrador del sistema.",
|
||||
"cancelFailedError": "No se pudieron eliminar los dictados. Actualice su pantalla e inténtelo nuevamente.",
|
||||
"deleteFailedError": "(es)タスクの削除に失敗しました。画面を更新し、再度ご確認ください。"
|
||||
},
|
||||
"label": {
|
||||
"title": "Dictado",
|
||||
@ -250,9 +259,9 @@
|
||||
"deleteDictation": "Borrar dictado",
|
||||
"selectedTranscriptionist": "Transcriptor seleccionado",
|
||||
"poolTranscriptionist": "Lista de transcriptor",
|
||||
"fileBackup": "(es)File Backup",
|
||||
"downloadForBackup": "(es)Download for backup",
|
||||
"applications": "(es)Applications",
|
||||
"fileBackup": "Copia de seguridad de archivos",
|
||||
"downloadForBackup": "Descargar para respaldo",
|
||||
"applications": "Aplicación de escritorio",
|
||||
"cancelDictation": "Cancelar transcripción"
|
||||
}
|
||||
},
|
||||
@ -415,7 +424,10 @@
|
||||
},
|
||||
"message": {
|
||||
"selectedTypistEmptyError": "Se deben seleccionar uno o más transcriptores para guardar un grupo de transcripción.",
|
||||
"groupSaveFailedError": "No se pudo guardar el grupo mecanógrafo. La información mostrada puede estar desactualizada, así que actualice la pantalla para ver el estado más reciente."
|
||||
"groupSaveFailedError": "El grupo transcriptor no se pudo salvar. La información mostrada puede estar desactualizada. Así que actualice la pantalla para ver el estado más reciente.",
|
||||
"GroupNameAlreadyExistError": "(es)このTranscriptionistGroup名は既に登録されています。他のTranscriptionistGroup名で登録してください。",
|
||||
"deleteFailedWorkflowAssigned": "(es)TranscriptionistGroupの削除に失敗しました。Workflow画面でルーティングルールから対象TranscriptionistGroupを外してください。",
|
||||
"deleteFailedCheckoutPermissionExisted": "(es)TranscriptionistGroupの削除に失敗しました。Dictation画面でタスクのルーティングから対象TranscriptionistGroupを外してください。"
|
||||
}
|
||||
},
|
||||
"worktypeIdSetting": {
|
||||
@ -471,11 +483,11 @@
|
||||
"addAccount": "Añadir cuenta",
|
||||
"name": "Nombre de empresa",
|
||||
"category": "Nivel de cuenta",
|
||||
"accountId": "ID de la cuenta",
|
||||
"accountId": "ID de autor",
|
||||
"country": "País",
|
||||
"primaryAdmin": "Administrador primario",
|
||||
"email": "Email",
|
||||
"dealerManagement": "Permitir que el concesionario realice los cambios",
|
||||
"dealerManagement": "Permitir que el distribuidor realice los cambios",
|
||||
"partners": "Socios",
|
||||
"deleteAccount": "Borrar cuenta"
|
||||
},
|
||||
@ -494,9 +506,9 @@
|
||||
"accountID": "ID de la cuenta",
|
||||
"yourCategory": "Tipo de cuenta",
|
||||
"yourCountry": "País",
|
||||
"yourDealer": "Concesionario",
|
||||
"selectDealer": "Seleccionar Concesionario",
|
||||
"dealerManagement": "Permitir que el concesionario realice los cambios",
|
||||
"yourDealer": "Distribuidor",
|
||||
"selectDealer": "Seleccionar distribuidor",
|
||||
"dealerManagement": "Permitir que el distribuidor realice los cambios",
|
||||
"administratorInformation": "Información del administrador",
|
||||
"primaryAdministrator": "Administrador primario",
|
||||
"secondaryAdministrator": "Administrador secundario",
|
||||
@ -507,6 +519,9 @@
|
||||
},
|
||||
"message": {
|
||||
"updateAccountFailedError": "No se pudo guardar la información de la cuenta. Actualice la pantalla e inténtelo de nuevo."
|
||||
},
|
||||
"text": {
|
||||
"dealerManagementAnnotation": "Al habilitar la opción \"Permitir que el distribuidor realice los cambios\", usted acepta permitir que su distribuidor tenga derechos para acceder a su cuenta de ODMS Cloud para solicitar licencias y registrar usuarios en su nombre. Su distribuidor no tendrá acceso a ningún archivo de voz o documento almacenado en su cuenta de ODMS Cloud."
|
||||
}
|
||||
},
|
||||
"deleteAccountPopup": {
|
||||
@ -538,22 +553,22 @@
|
||||
},
|
||||
"supportPage": {
|
||||
"label": {
|
||||
"title": "(es)Support",
|
||||
"howToUse": "(es)How to use the system",
|
||||
"title": "Soporte",
|
||||
"howToUse": "Cómo utilizar el sistema",
|
||||
"supportPageEnglish": "OMDS Cloud User Guide",
|
||||
"supportPageGerman": "OMDS Cloud Benutzerhandbuch",
|
||||
"supportPageFrench": "OMDS Cloud Mode d'emploi",
|
||||
"supportPageSpanish": "OMDS Cloud Guía del usuario"
|
||||
"supportPageGerman": "OMDS Cloud-Benutzerhandbuch",
|
||||
"supportPageFrench": "Guía del usuario de la nube OMDS",
|
||||
"supportPageSpanish": "Guide de l'utilisateur du cloud OMDS"
|
||||
},
|
||||
"text": {
|
||||
"notResolved": "(es)If the problem persists even after referring to the user guide, please contact a higher-level person in charge."
|
||||
"notResolved": "Consulte la Guía del usuario para obtener información sobre las funciones de ODMS Cloud. Si necesita soporte adicional, comuníquese con su administrador o revendedor certificado de ODMS Cloud."
|
||||
}
|
||||
},
|
||||
"filePropertyPopup": {
|
||||
"label": {
|
||||
"general": "(es)General",
|
||||
"job": "(es)Job",
|
||||
"close": "(es)Close"
|
||||
"general": "General",
|
||||
"job": "Trabajo",
|
||||
"close": "Cerrar"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,11 +24,11 @@
|
||||
"headerDictations": "Dictées",
|
||||
"headerWorkflow": "Flux de travail",
|
||||
"headerPartners": "Partenaires",
|
||||
"headerSupport": "(fr)Support",
|
||||
"headerSupport": "Support",
|
||||
"tier1": "Admin",
|
||||
"tier2": "BC",
|
||||
"tier3": "Distributeur",
|
||||
"tier4": "Concessionnaire",
|
||||
"tier4": "Revendeur",
|
||||
"tier5": "Client",
|
||||
"notSelected": "Aucune",
|
||||
"signOutButton": "se déconnecter"
|
||||
@ -62,14 +62,14 @@
|
||||
"title": "Créez votre compte",
|
||||
"accountInfoTitle": "Information d'inscription",
|
||||
"countryExplanation": "Sélectionnez le pays où vous vous trouvez. Si votre pays ne figure pas dans la liste, veuillez sélectionner le pays le plus proche.",
|
||||
"dealerExplanation": "Veuillez sélectionner le concessionnaire auprès duquel vous souhaitez acheter la licence.",
|
||||
"dealerExplanation": "Veuillez sélectionner le revendeur auprès duquel vous souhaitez acheter la licence.",
|
||||
"adminInfoTitle": "Enregistrer les informations de l'administrateur principal",
|
||||
"passwordTerms": "Veuillez définir un mot de passe. Le mot de passe doit être composé de 8 à 25 caractères et doit contenir des lettres, des chiffres et des symboles. (Devrait lister les symboles compatibles et indiquer si une majuscule est nécessaire)."
|
||||
},
|
||||
"label": {
|
||||
"company": "Nom de l'entreprise",
|
||||
"country": "Pays",
|
||||
"dealer": "Concessionnaire (Facultatif)",
|
||||
"dealer": "Revendeur (Facultatif)",
|
||||
"adminName": "Nom de l'administrateur",
|
||||
"email": "Adresse e-mail",
|
||||
"password": "Mot de passe",
|
||||
@ -93,7 +93,7 @@
|
||||
"label": {
|
||||
"company": "Nom de l'entreprise",
|
||||
"country": "Pays",
|
||||
"dealer": "Concessionnaire (Facultatif)",
|
||||
"dealer": "Revendeur (Facultatif)",
|
||||
"adminName": "Nom de l'administrateur",
|
||||
"email": "Adresse e-mail",
|
||||
"password": "Mot de passe",
|
||||
@ -128,7 +128,14 @@
|
||||
"authorIdIncorrectError": "Le format de l'identifiant de l'auteur n'est pas valide. Seuls les caractères alphanumériques et \"_\" peuvent être saisis pour l'ID d'auteur.",
|
||||
"roleChangeError": "Impossible de modifier le rôle de l'utilisateur. Les informations affichées peuvent être obsolètes, veuillez donc actualiser l'écran pour voir le dernier statut.",
|
||||
"encryptionPasswordCorrectError": "Le mot de passe de cryptage n'est pas conforme aux règles.",
|
||||
"alreadyLicenseDeallocatedError": "La licence attribuée a déjà été annulée. Les informations affichées peuvent être obsolètes, veuillez donc actualiser l'écran pour voir le dernier statut."
|
||||
"alreadyLicenseDeallocatedError": "La licence attribuée a déjà été annulée. Les informations affichées peuvent être obsolètes, veuillez donc actualiser l'écran pour voir le dernier statut.",
|
||||
"UserDeletionLicenseActiveError": "(fr)ユーザーの削除に失敗しました。対象ユーザーのライセンス割り当てを解除してください。",
|
||||
"TypistDeletionRoutingRuleError": "(fr)ユーザーの削除に失敗しました。Workflow画面でルーティングルールから対象Transcriptionistを外してください。",
|
||||
"AdminUserDeletionError": "(fr)ユーザーの削除に失敗しました。アカウント画面で対象ユーザーをPrimary/Secondary Administratorから外してください。",
|
||||
"TypistUserDeletionTranscriptionTaskError": "(fr)ユーザーの削除に失敗しました。Dictation画面でタスクのルーティングから対象Transcriptionistを外してください。",
|
||||
"AuthorUserDeletionTranscriptionTaskError": "(fr)ユーザーの削除に失敗しました。Dictation画面で対象AuthorのAuthorIDが設定されているタスクの中で、文字起こしが未完了のタスクを削除またはFinishedにしてください。",
|
||||
"TypistUserDeletionTranscriptionistGroupError": "(fr)ユーザーの削除に失敗しました。Workflow画面でTranscriptionistGroupから対象Transcriptionistを外してください。",
|
||||
"AuthorDeletionRoutingRuleError": "(fr)ユーザーの削除に失敗しました。Workflow画面でルーティングルールから対象AuthorのAuthorIDを外してください。"
|
||||
},
|
||||
"label": {
|
||||
"title": "Utilisateur",
|
||||
@ -180,8 +187,8 @@
|
||||
"storageSize": "Stockage disponible",
|
||||
"usedSize": "Stockage utilisé",
|
||||
"storageAvailable": "Stockage indisponible (montant dépassée)",
|
||||
"licenseLabel": "(fr)License",
|
||||
"storageLabel": "(fr)Storage"
|
||||
"licenseLabel": "Licence",
|
||||
"storageLabel": "Stockage"
|
||||
}
|
||||
},
|
||||
"licenseOrderPage": {
|
||||
@ -190,7 +197,8 @@
|
||||
"poNumberIncorrectError": "Le format du numéro de bon de commande n'est pas valide. Seuls des caractères alphanumériques peuvent être saisis pour le numéro de bon de commande.",
|
||||
"newOrderIncorrectError": "Veuillez saisir un nombre supérieur ou égal à 1 pour la nouvelle commande.",
|
||||
"confirmOrder": "Voulez-vous passer commande?",
|
||||
"poNumberConflictError": "Le numéro de bon de commande saisi existe déjà. Veuillez saisir un autre numéro de bon de commande."
|
||||
"poNumberConflictError": "Le numéro de bon de commande saisi existe déjà. Veuillez saisir un autre numéro de bon de commande.",
|
||||
"dealerNotFoundError": "(fr)ディーラーが設定されていないため、ライセンスを注文できません。アカウント画面でディーラーを指定してください。"
|
||||
},
|
||||
"label": {
|
||||
"title": "Commander licence",
|
||||
@ -206,8 +214,9 @@
|
||||
"noPlaybackAuthorization": "Vous n'êtes pas autorisé à lire ce fichier.",
|
||||
"taskToPlaybackNoExists": "Le fichier ne peut pas être lu car il a déjà été transcrit ou n'existe pas.",
|
||||
"taskNotEditable": "Le transcripteur ne peut pas être changé car la transcription est déjà en cours ou le fichier n'existe pas. Veuillez actualiser l'écran et vérifier le dernier statut.",
|
||||
"backupFailedError": "(fr)ファイルのバックアップに失敗したため処理を中断しました。時間をおいて再実行しても解決しない場合はシステム管理者にお問い合わせください。",
|
||||
"cancelFailedError": "(fr)タスクのキャンセルに失敗しました。画面を更新し、再度ご確認ください。"
|
||||
"backupFailedError": "Le processus de « Sauvegarde de fichier » a échoué. Veuillez réessayer plus tard. Si l'erreur persiste, contactez votre administrateur système.",
|
||||
"cancelFailedError": "Échec de la suppression des dictées. Veuillez actualiser votre écran et réessayer.",
|
||||
"deleteFailedError": "(fr)タスクの削除に失敗しました。画面を更新し、再度ご確認ください。"
|
||||
},
|
||||
"label": {
|
||||
"title": "Dictées",
|
||||
@ -250,9 +259,9 @@
|
||||
"deleteDictation": "Supprimer la dictée",
|
||||
"selectedTranscriptionist": "Transcriptionniste sélectionné",
|
||||
"poolTranscriptionist": "Liste de transcriptionniste",
|
||||
"fileBackup": "(fr)File Backup",
|
||||
"downloadForBackup": "(fr)Download for backup",
|
||||
"applications": "(fr)Applications",
|
||||
"fileBackup": "Sauvegarde de fichiers",
|
||||
"downloadForBackup": "Télécharger pour sauvegarde",
|
||||
"applications": "Application de bureau",
|
||||
"cancelDictation": "Annuler la transcription"
|
||||
}
|
||||
},
|
||||
@ -415,7 +424,10 @@
|
||||
},
|
||||
"message": {
|
||||
"selectedTypistEmptyError": "Un ou plusieurs transcripteurs doivent être sélectionnés pour enregistrer un groupe de transcription.",
|
||||
"groupSaveFailedError": "Le groupe de dactylographes n'a pas pu être enregistré. Les informations affichées peuvent être obsolètes, veuillez donc actualiser l'écran pour voir le dernier statut."
|
||||
"groupSaveFailedError": "Le groupe de transcriptionniste n'a pas pu être enregistré. Les informations affichées peuvent être obsolètes, veuillez donc actualiser l'écran pour voir le dernier statut.",
|
||||
"GroupNameAlreadyExistError": "(fr)このTranscriptionistGroup名は既に登録されています。他のTranscriptionistGroup名で登録してください。",
|
||||
"deleteFailedWorkflowAssigned": "(fr)TranscriptionistGroupの削除に失敗しました。Workflow画面でルーティングルールから対象TranscriptionistGroupを外してください。",
|
||||
"deleteFailedCheckoutPermissionExisted": "(fr)TranscriptionistGroupの削除に失敗しました。Dictation画面でタスクのルーティングから対象TranscriptionistGroupを外してください。"
|
||||
}
|
||||
},
|
||||
"worktypeIdSetting": {
|
||||
@ -471,11 +483,11 @@
|
||||
"addAccount": "Ajouter compte",
|
||||
"name": "Nom de l'entreprise",
|
||||
"category": "Niveau compte",
|
||||
"accountId": "identifiant de compte",
|
||||
"accountId": "Identifiant Auteur",
|
||||
"country": "Pays",
|
||||
"primaryAdmin": "Administrateur principal",
|
||||
"email": "Email",
|
||||
"dealerManagement": "Autoriser le concessionnaire à modifier les paramètres",
|
||||
"dealerManagement": "Autoriser le revendeur à modifier les paramètres",
|
||||
"partners": "Partenaires",
|
||||
"deleteAccount": "Supprimer le compte"
|
||||
},
|
||||
@ -494,9 +506,9 @@
|
||||
"accountID": "identifiant de compte",
|
||||
"yourCategory": "Type de compte",
|
||||
"yourCountry": "Pays",
|
||||
"yourDealer": "Concessionnaire",
|
||||
"selectDealer": "Sélectionner le Concessionnaire",
|
||||
"dealerManagement": "Autoriser le concessionnaire à modifier les paramètres",
|
||||
"yourDealer": "Revendeur",
|
||||
"selectDealer": "Sélectionner le revendeur",
|
||||
"dealerManagement": "Autoriser le revendeur à modifier les paramètres",
|
||||
"administratorInformation": "Informations sur l'administrateur",
|
||||
"primaryAdministrator": "Administrateur principal",
|
||||
"secondaryAdministrator": "Administrateur secondaire",
|
||||
@ -507,6 +519,9 @@
|
||||
},
|
||||
"message": {
|
||||
"updateAccountFailedError": "Échec de l'enregistrement des informations du compte. Veuillez actualiser l'écran et réessayer."
|
||||
},
|
||||
"text": {
|
||||
"dealerManagementAnnotation": "En activant l'option « Autoriser le revendeur à modifier les paramètres », vous acceptez que votre concessionnaire ait les droits d'accès à votre compte ODMS Cloud pour commander des licences et enregistrer des utilisateurs en votre nom. Votre revendeur n'aura accès à aucun fichier(s) vocal(s) ou document(s) stocké(s) dans votre compte ODMS Cloud."
|
||||
}
|
||||
},
|
||||
"deleteAccountPopup": {
|
||||
@ -538,22 +553,22 @@
|
||||
},
|
||||
"supportPage": {
|
||||
"label": {
|
||||
"title": "(fr)Support",
|
||||
"howToUse": "(fr)How to use the system",
|
||||
"title": "Support",
|
||||
"howToUse": "Comment utiliser le système",
|
||||
"supportPageEnglish": "OMDS Cloud User Guide",
|
||||
"supportPageGerman": "OMDS Cloud Benutzerhandbuch",
|
||||
"supportPageFrench": "OMDS Cloud Mode d'emploi",
|
||||
"supportPageSpanish": "OMDS Cloud Guía del usuario"
|
||||
"supportPageGerman": "OMDS Cloud-Benutzerhandbuch",
|
||||
"supportPageFrench": "Guía del usuario de la nube OMDS",
|
||||
"supportPageSpanish": "Guide de l'utilisateur du cloud OMDS"
|
||||
},
|
||||
"text": {
|
||||
"notResolved": "(fr)If the problem persists even after referring to the user guide, please contact a higher-level person in charge."
|
||||
"notResolved": "Veuillez vous référer au Guide de l'utilisateur pour plus d'informations sur les fonctionnalités d'ODMS Cloud. Si vous avez besoin d'une assistance supplémentaire, veuillez contacter votre administrateur ou votre revendeur certifié ODMS Cloud."
|
||||
}
|
||||
},
|
||||
"filePropertyPopup": {
|
||||
"label": {
|
||||
"general": "(fr)General",
|
||||
"job": "(fr)Job",
|
||||
"close": "(fr)Close"
|
||||
"general": "Général",
|
||||
"job": "Tâches",
|
||||
"close": "Fermer"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ MAIL_FROM=xxxxx@xxxxx.xxxx
|
||||
NOTIFICATION_HUB_NAME=ntf-odms-dev
|
||||
NOTIFICATION_HUB_CONNECT_STRING=XXXXXXXXXXXXXXXXXX
|
||||
APP_DOMAIN=http://localhost:8081/
|
||||
STORAGE_TOKEN_EXPIRE_TIME=30
|
||||
STORAGE_TOKEN_EXPIRE_TIME=2
|
||||
STORAGE_ACCOUNT_NAME_US=saodmsusdev
|
||||
STORAGE_ACCOUNT_NAME_AU=saodmsaudev
|
||||
STORAGE_ACCOUNT_NAME_EU=saodmseudev
|
||||
@ -26,10 +26,10 @@ STORAGE_ACCOUNT_KEY_EU=XXXXXXXXXXXXXXXXXXXXXXX
|
||||
STORAGE_ACCOUNT_ENDPOINT_US=https://AAAAAAAAAAAAA
|
||||
STORAGE_ACCOUNT_ENDPOINT_AU=https://AAAAAAAAAAAAA
|
||||
STORAGE_ACCOUNT_ENDPOINT_EU=https://AAAAAAAAAAAAA
|
||||
ACCESS_TOKEN_LIFETIME_WEB=7200000
|
||||
REFRESH_TOKEN_LIFETIME_WEB=86400000
|
||||
REFRESH_TOKEN_LIFETIME_DEFAULT=2592000000
|
||||
EMAIL_CONFIRM_LIFETIME=86400000
|
||||
ACCESS_TOKEN_LIFETIME_WEB=7200
|
||||
REFRESH_TOKEN_LIFETIME_WEB=86400
|
||||
REFRESH_TOKEN_LIFETIME_DEFAULT=2592000
|
||||
EMAIL_CONFIRM_LIFETIME=86400
|
||||
REDIS_HOST=redis-cache
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=omdsredispass
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
-- +migrate Up
|
||||
ALTER TABLE `accounts` ADD INDEX `idx_accounts_tier` (tier);
|
||||
ALTER TABLE `accounts` ADD INDEX `idx_accounts_parent_account_id` (parent_account_id);
|
||||
ALTER TABLE `users` ADD INDEX `idx_users_external_id` (external_id);
|
||||
ALTER TABLE `users` ADD INDEX `idx_users_email_verified` (email_verified);
|
||||
ALTER TABLE `licenses` ADD INDEX `idx_licenses_order_id` (order_id);
|
||||
ALTER TABLE `licenses` ADD INDEX `idx_licenses_status` (status);
|
||||
ALTER TABLE `template_files` ADD INDEX `idx_template_files_account_id` (account_id);
|
||||
ALTER TABLE `template_files` ADD INDEX `idx_template_files_file_name` (file_name(500));
|
||||
|
||||
-- +migrate Down
|
||||
ALTER TABLE `accounts` DROP INDEX `idx_accounts_tier`;
|
||||
ALTER TABLE `accounts` DROP INDEX `idx_accounts_parent_account_id`;
|
||||
ALTER TABLE `users` DROP INDEX `idx_users_external_id`;
|
||||
ALTER TABLE `users` DROP INDEX `idx_users_email_verified`;
|
||||
ALTER TABLE `licenses` DROP INDEX `idx_licenses_order_id`;
|
||||
ALTER TABLE `licenses` DROP INDEX `idx_licenses_status`;
|
||||
ALTER TABLE `template_files` DROP INDEX `idx_template_files_account_id`;
|
||||
ALTER TABLE `template_files` DROP INDEX `idx_template_files_file_name`;
|
||||
13
dictation_server/db/migrations/054-add_license_index.sql
Normal file
13
dictation_server/db/migrations/054-add_license_index.sql
Normal file
@ -0,0 +1,13 @@
|
||||
-- +migrate Up
|
||||
ALTER TABLE `license_orders` ADD INDEX `idx_po_number` (po_number);
|
||||
ALTER TABLE `license_orders` ADD INDEX `idx_from_account_id` (from_account_id);
|
||||
ALTER TABLE `license_orders` ADD INDEX `idx_status` (status);
|
||||
ALTER TABLE `card_licenses` ADD INDEX `idx_card_license_key` (card_license_key);
|
||||
ALTER TABLE `licenses` ADD INDEX `idx_status` (status);
|
||||
|
||||
-- +migrate Down
|
||||
ALTER TABLE `license_orders` DROP INDEX `idx_po_number`;
|
||||
ALTER TABLE `license_orders` DROP INDEX `idx_from_account_id`;
|
||||
ALTER TABLE `license_orders` DROP INDEX `idx_status`;
|
||||
ALTER TABLE `card_licenses` DROP INDEX `idx_card_license_key`;
|
||||
ALTER TABLE `licenses` DROP INDEX `idx_status`;
|
||||
5
dictation_server/db/migrations/055-add_users_index.sql
Normal file
5
dictation_server/db/migrations/055-add_users_index.sql
Normal file
@ -0,0 +1,5 @@
|
||||
-- +migrate Up
|
||||
ALTER TABLE `users` ADD INDEX `idx_role` (role);
|
||||
|
||||
-- +migrate Down
|
||||
ALTER TABLE `users` DROP INDEX `idx_role`;
|
||||
27
dictation_server/db/migrations/056-add_tasks_index.sql
Normal file
27
dictation_server/db/migrations/056-add_tasks_index.sql
Normal file
@ -0,0 +1,27 @@
|
||||
-- +migrate Up
|
||||
ALTER TABLE `tasks` ADD INDEX `idx_tasks_audio_file_id` (audio_file_id);
|
||||
ALTER TABLE `tasks` ADD INDEX `idx_tasks_status` (status);
|
||||
ALTER TABLE `tasks` ADD INDEX `idx_tasks_typist_user_id` (typist_user_id);
|
||||
ALTER TABLE `tasks` ADD INDEX `idx_tasks_is_job_number_enabled` (is_job_number_enabled);
|
||||
ALTER TABLE `checkout_permission` ADD INDEX `idx_checkout_permission_task_id` (task_id);
|
||||
ALTER TABLE `checkout_permission` ADD INDEX `idx_checkout_permission_user_group_id` (user_group_id);
|
||||
ALTER TABLE `checkout_permission` ADD INDEX `idx_checkout_permission_user_id` (user_id);
|
||||
ALTER TABLE `users` ADD INDEX `idx_users_role` (role);
|
||||
ALTER TABLE `users` ADD INDEX `idx_users_author_id` (author_id);
|
||||
ALTER TABLE `users` ADD INDEX `idx_users_deleted_at` (deleted_at);
|
||||
ALTER TABLE `worktypes` ADD INDEX `idx_worktypes_custom_worktype_id` (custom_worktype_id);
|
||||
ALTER TABLE `workflows` ADD INDEX `idx_workflows_account_id` (account_id);
|
||||
|
||||
-- +migrate Down
|
||||
ALTER TABLE `tasks` DROP INDEX `idx_tasks_audio_file_id`;
|
||||
ALTER TABLE `tasks` DROP INDEX `idx_tasks_status`;
|
||||
ALTER TABLE `tasks` DROP INDEX `idx_tasks_typist_user_id`;
|
||||
ALTER TABLE `tasks` DROP INDEX `idx_tasks_is_job_number_enabled`;
|
||||
ALTER TABLE `checkout_permission` DROP INDEX `idx_checkout_permission_task_id`;
|
||||
ALTER TABLE `checkout_permission` DROP INDEX `idx_checkout_permission_user_group_id`;
|
||||
ALTER TABLE `checkout_permission` DROP INDEX `idx_checkout_permission_user_id`;
|
||||
ALTER TABLE `users` DROP INDEX `idx_users_role`;
|
||||
ALTER TABLE `users` DROP INDEX `idx_users_author_id`;
|
||||
ALTER TABLE `users` DROP INDEX `idx_users_deleted_at`;
|
||||
ALTER TABLE `worktypes` DROP INDEX `idx_worktypes_custom_worktype_id`;
|
||||
ALTER TABLE `workflows` DROP INDEX `idx_workflows_account_id`;
|
||||
@ -0,0 +1,6 @@
|
||||
-- +migrate Up
|
||||
ALTER TABLE `user_group` ADD UNIQUE `unique_index_account_id_name` (`account_id`, `name`);
|
||||
|
||||
|
||||
-- +migrate Down
|
||||
ALTER TABLE `user_group` DROP INDEX `unique_index_account_id_name`;
|
||||
@ -59,6 +59,7 @@ export const ErrorCodes = [
|
||||
'E010811', // ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合)
|
||||
'E010812', // ライセンス未割当エラー
|
||||
'E010908', // タイピストグループ不在エラー
|
||||
'E010909', // タイピストグループ名重複エラー
|
||||
'E011001', // ワークタイプ重複エラー
|
||||
'E011002', // ワークタイプ登録上限超過エラー
|
||||
'E011003', // ワークタイプ不在エラー
|
||||
|
||||
@ -48,6 +48,7 @@ export const errors: Errors = {
|
||||
E010811: 'Already license allocated Error',
|
||||
E010812: 'License not allocated Error',
|
||||
E010908: 'Typist Group not exist Error',
|
||||
E010909: 'Typist Group name already exist Error',
|
||||
E011001: 'This WorkTypeID already used Error',
|
||||
E011002: 'WorkTypeID create limit exceeded Error',
|
||||
E011003: 'WorkTypeID not found Error',
|
||||
|
||||
@ -82,7 +82,8 @@ export const overrideSendgridService = <TService>(
|
||||
overrides: {
|
||||
sendMail?: (
|
||||
context: Context,
|
||||
to: string,
|
||||
to: string[],
|
||||
cc: string[],
|
||||
from: string,
|
||||
subject: string,
|
||||
text: string,
|
||||
|
||||
@ -219,9 +219,9 @@ export const PNS = {
|
||||
};
|
||||
|
||||
/**
|
||||
* ユーザーのライセンス状態
|
||||
* ユーザーのライセンスの有効期限の状態
|
||||
*/
|
||||
export const USER_LICENSE_STATUS = {
|
||||
export const USER_LICENSE_EXPIRY_STATUS = {
|
||||
NORMAL: 'Normal',
|
||||
NO_LICENSE: 'NoLicense',
|
||||
ALERT: 'Alert',
|
||||
@ -311,3 +311,13 @@ export const USER_AUDIO_FORMAT = 'DS2(QP)';
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const NODE_ENV_TEST = 'test';
|
||||
|
||||
/**
|
||||
* ユーザに対するライセンスの状態
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const USER_LICENSE_STATUS = {
|
||||
UNALLOCATED: 'unallocated',
|
||||
ALLOCATED: 'allocated',
|
||||
EXPIRED: 'expired',
|
||||
} as const;
|
||||
|
||||
@ -134,7 +134,26 @@ describe('createAccount', () => {
|
||||
},
|
||||
});
|
||||
|
||||
overrideSendgridService(service, {});
|
||||
let _subject: string = "";
|
||||
let _url: string | undefined = "";
|
||||
overrideSendgridService(service, {
|
||||
sendMail: async (
|
||||
context: Context,
|
||||
to: string[],
|
||||
cc: string[],
|
||||
from: string,
|
||||
subject: string,
|
||||
text: string,
|
||||
html: string,
|
||||
) => {
|
||||
const urlPattern = /https?:\/\/[^\s]+/g;
|
||||
const urls = text.match(urlPattern);
|
||||
const url = urls?.pop();
|
||||
|
||||
_subject = subject;
|
||||
_url = url;
|
||||
},
|
||||
});
|
||||
overrideBlobstorageService(service, {
|
||||
createContainer: async () => {
|
||||
return;
|
||||
@ -175,6 +194,10 @@ describe('createAccount', () => {
|
||||
expect(user?.accepted_dpa_version).toBe(acceptedDpaVersion);
|
||||
expect(user?.account_id).toBe(accountId);
|
||||
expect(user?.role).toBe(role);
|
||||
|
||||
// 想定通りのメールが送られているか確認
|
||||
expect(_subject).toBe('User Registration Notification [U-102]');
|
||||
expect(_url?.startsWith('http://localhost:8081/mail-confirm?verify=')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('アカウントを作成がAzure AD B2Cへの通信失敗によって失敗すると500エラーが発生する', async () => {
|
||||
@ -5704,9 +5727,39 @@ describe('アカウント情報更新', () => {
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
let _subject: string = "";
|
||||
let _url: string | undefined = "";
|
||||
overrideSendgridService(service, {
|
||||
sendMail: async () => {
|
||||
return;
|
||||
sendMail: async (
|
||||
context: Context,
|
||||
to: string[],
|
||||
cc: string[],
|
||||
from: string,
|
||||
subject: string,
|
||||
text: string,
|
||||
html: string,
|
||||
) => {
|
||||
const urlPattern = /https?:\/\/[^\s]+/g;
|
||||
const urls = text.match(urlPattern);
|
||||
const url = urls?.pop();
|
||||
|
||||
_subject = subject;
|
||||
_url = url;
|
||||
},
|
||||
});
|
||||
overrideAdB2cService(service, {
|
||||
getUser: async (context, externalId) => {
|
||||
return {
|
||||
displayName: 'TEMP' + externalId,
|
||||
id: externalId,
|
||||
identities: [
|
||||
{
|
||||
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
|
||||
issuer: 'xxxxxx',
|
||||
issuerAssignedId: 'mail@example.com',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@ -5733,6 +5786,9 @@ describe('アカウント情報更新', () => {
|
||||
expect(account?.delegation_permission).toBe(true);
|
||||
expect(account?.primary_admin_user_id).toBe(tier5Accounts.admin.id);
|
||||
expect(account?.secondary_admin_user_id).toBe(null);
|
||||
// 想定通りのメールが送られているか確認
|
||||
expect(_subject).toBe('Account Edit Notification [U-112]');
|
||||
expect(_url).toBe('http://localhost:8081/');
|
||||
});
|
||||
it('アカウント情報を更新する(第五階層以外が実行)', async () => {
|
||||
if (!source) fail();
|
||||
@ -6364,7 +6420,27 @@ describe('deleteAccountAndData', () => {
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
overrideSendgridService(service, {});
|
||||
let _subject: string = '';
|
||||
let _url: string | undefined = '';
|
||||
overrideSendgridService(service, {
|
||||
sendMail: async (
|
||||
context: Context,
|
||||
to: string[],
|
||||
cc: string[],
|
||||
from: string,
|
||||
subject: string,
|
||||
text: string,
|
||||
html: string,
|
||||
) => {
|
||||
const urlPattern = /https?:\/\/[^\s]+/g;
|
||||
const urls = text.match(urlPattern);
|
||||
const url = urls?.pop();
|
||||
|
||||
_subject = subject;
|
||||
_url = url;
|
||||
},
|
||||
});
|
||||
|
||||
// 第一~第四階層のアカウント作成
|
||||
const {
|
||||
tier1Accounts: tier1Accounts,
|
||||
@ -6485,10 +6561,36 @@ describe('deleteAccountAndData', () => {
|
||||
licensesB[0].id,
|
||||
);
|
||||
|
||||
// ADB2Cユーザーの削除成功
|
||||
overrideAdB2cService(service, {
|
||||
getUser: async (context, externalId) => {
|
||||
return {
|
||||
displayName: 'TEMP' + externalId,
|
||||
id: externalId,
|
||||
identities: [
|
||||
{
|
||||
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
|
||||
issuer: 'xxxxxx',
|
||||
issuerAssignedId: 'mail@example.com',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
getUsers: async (context, externalIds) => {
|
||||
return externalIds.map((x) => ({
|
||||
displayName: 'admin',
|
||||
id: x,
|
||||
identities: [
|
||||
{
|
||||
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
|
||||
issuer: 'xxxxxx',
|
||||
issuerAssignedId: `mail+${x}@example.com`,
|
||||
},
|
||||
],
|
||||
}));
|
||||
},
|
||||
deleteUsers: jest.fn(),
|
||||
});
|
||||
|
||||
// blobstorageコンテナの削除成功
|
||||
overrideBlobstorageService(service, {
|
||||
deleteContainer: jest.fn(),
|
||||
@ -6559,6 +6661,9 @@ describe('deleteAccountAndData', () => {
|
||||
const LicenseAllocationHistoryArchive =
|
||||
await getLicenseAllocationHistoryArchive(source);
|
||||
expect(LicenseAllocationHistoryArchive.length).toBe(1);
|
||||
|
||||
expect(_subject).toBe('Account Deleted Notification [U-111]');
|
||||
expect(_url).toBe('http://localhost:8081/');
|
||||
});
|
||||
it('アカウントの削除に失敗した場合はエラーを返す', async () => {
|
||||
if (!source) fail();
|
||||
|
||||
@ -60,6 +60,7 @@ import {
|
||||
} from '../../repositories/licenses/errors/types';
|
||||
import { BlobstorageService } from '../../gateways/blobstorage/blobstorage.service';
|
||||
import {
|
||||
TypistGroupNameAlreadyExistError,
|
||||
TypistGroupNotExistError,
|
||||
TypistIdInvalidError,
|
||||
} from '../../repositories/user_groups/errors/types';
|
||||
@ -1241,6 +1242,12 @@ export class AccountsService {
|
||||
makeErrorResponse('E010204'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
// 同名のタイピストグループが存在する場合は400エラーを返す
|
||||
case TypistGroupNameAlreadyExistError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010909'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
default:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
@ -1315,6 +1322,12 @@ export class AccountsService {
|
||||
makeErrorResponse('E010908'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
// 同名のタイピストグループが存在する場合は400エラーを返す
|
||||
case TypistGroupNameAlreadyExistError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010909'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
default:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
|
||||
@ -198,7 +198,7 @@ export class CancelIssueRequest {
|
||||
export class CreateWorktypesRequest {
|
||||
@ApiProperty({ minLength: 1, maxLength: 255, description: 'WorktypeID' })
|
||||
@MinLength(1)
|
||||
@MaxLength(255)
|
||||
@MaxLength(16)
|
||||
@IsRecorderAllowed()
|
||||
worktypeId: string;
|
||||
@ApiProperty({ description: 'Worktypeの説明', required: false })
|
||||
@ -210,7 +210,7 @@ export class CreateWorktypesRequest {
|
||||
export class UpdateWorktypesRequest {
|
||||
@ApiProperty({ minLength: 1, description: 'WorktypeID' })
|
||||
@MinLength(1)
|
||||
@MaxLength(255)
|
||||
@MaxLength(16)
|
||||
@IsRecorderAllowed()
|
||||
worktypeId: string;
|
||||
@ApiProperty({ description: 'Worktypeの説明', required: false })
|
||||
|
||||
@ -235,67 +235,6 @@ describe('publishUploadSas', () => {
|
||||
new HttpException(makeErrorResponse('E010812'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
it('アップロード時にユーザーに割り当てられたライセンスが有効期限切れの場合エラー(第五階層限定)', async () => {
|
||||
if (!source) fail();
|
||||
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する
|
||||
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
|
||||
source,
|
||||
);
|
||||
const tier5Accounts = await makeTestAccount(source, {
|
||||
parent_account_id: tier4Accounts[0].account.id,
|
||||
tier: 5,
|
||||
});
|
||||
const {
|
||||
external_id: externalId,
|
||||
id: userId,
|
||||
author_id: authorId,
|
||||
} = await makeTestUser(source, {
|
||||
account_id: tier5Accounts.account.id,
|
||||
external_id: 'author-user-external-id',
|
||||
role: 'author',
|
||||
author_id: 'AUTHOR_ID',
|
||||
});
|
||||
// 昨日の日付を作成
|
||||
let yesterday = new Date();
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
yesterday = new DateWithZeroTime(yesterday);
|
||||
// 期限切れのライセンスを作成して紐づける
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
yesterday,
|
||||
tier5Accounts.account.id,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
userId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
blobParam.publishUploadSas = `${url}?sas-token`;
|
||||
blobParam.fileExists = false;
|
||||
|
||||
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlobAndNotification(
|
||||
source,
|
||||
blobParam,
|
||||
notificationParam,
|
||||
);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
service.publishUploadSas(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
externalId,
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010805'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('タスク作成から自動ルーティング(DB使用)', () => {
|
||||
@ -1097,76 +1036,6 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
),
|
||||
).toEqual(`${url}?sas-token`);
|
||||
});
|
||||
it('ダウンロードSASトークンが乗っているURLを取得できる(第五階層の場合ライセンスのチェックを行う)', async () => {
|
||||
if (!source) fail();
|
||||
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する
|
||||
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
|
||||
source,
|
||||
);
|
||||
const tier5Accounts = await makeTestAccount(source, {
|
||||
parent_account_id: tier4Accounts[0].account.id,
|
||||
tier: 5,
|
||||
});
|
||||
const {
|
||||
external_id: externalId,
|
||||
id: userId,
|
||||
author_id: authorId,
|
||||
} = await makeTestUser(source, {
|
||||
account_id: tier5Accounts.account.id,
|
||||
external_id: 'author-user-external-id',
|
||||
role: 'author',
|
||||
author_id: 'AUTHOR_ID',
|
||||
});
|
||||
// 本日の日付を作成
|
||||
let today = new Date();
|
||||
today.setDate(today.getDate());
|
||||
today = new DateWithZeroTime(today);
|
||||
// 有効期限内のライセンスを作成して紐づける
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
today,
|
||||
tier5Accounts.account.id,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
userId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
|
||||
|
||||
const { audioFileId } = await createTask(
|
||||
source,
|
||||
tier5Accounts.account.id,
|
||||
url,
|
||||
'test.zip',
|
||||
'InProgress',
|
||||
undefined,
|
||||
authorId ?? '',
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlobAndNotification(
|
||||
source,
|
||||
blobParam,
|
||||
notificationParam,
|
||||
);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
expect(
|
||||
await service.publishAudioFileDownloadSas(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
externalId,
|
||||
audioFileId,
|
||||
),
|
||||
).toEqual(`${url}?sas-token`);
|
||||
});
|
||||
it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
@ -1396,133 +1265,6 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
new HttpException(makeErrorResponse('E010701'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
it('ダウンロード時にユーザーにライセンスが未割当の場合エラーとなる(第五階層限定)', async () => {
|
||||
if (!source) fail();
|
||||
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する(ライセンスは作成しない)
|
||||
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
|
||||
source,
|
||||
);
|
||||
const tier5Accounts = await makeTestAccount(source, {
|
||||
parent_account_id: tier4Accounts[0].account.id,
|
||||
tier: 5,
|
||||
});
|
||||
const {
|
||||
external_id: externalId,
|
||||
id: userId,
|
||||
author_id: authorId,
|
||||
} = await makeTestUser(source, {
|
||||
account_id: tier5Accounts.account.id,
|
||||
external_id: 'author-user-external-id',
|
||||
role: 'author',
|
||||
author_id: 'AUTHOR_ID',
|
||||
});
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
|
||||
|
||||
const { audioFileId } = await createTask(
|
||||
source,
|
||||
tier5Accounts.account.id,
|
||||
url,
|
||||
'test.zip',
|
||||
'InProgress',
|
||||
undefined,
|
||||
authorId ?? '',
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||
blobParam.fileExists = false;
|
||||
|
||||
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlobAndNotification(
|
||||
source,
|
||||
blobParam,
|
||||
notificationParam,
|
||||
);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
service.publishAudioFileDownloadSas(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
externalId,
|
||||
audioFileId,
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010812'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
it('ダウンロード時にユーザーに割り当てられたライセンスが有効期限切れの場合エラー(第五階層限定)', async () => {
|
||||
if (!source) fail();
|
||||
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する
|
||||
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
|
||||
source,
|
||||
);
|
||||
const tier5Accounts = await makeTestAccount(source, {
|
||||
parent_account_id: tier4Accounts[0].account.id,
|
||||
tier: 5,
|
||||
});
|
||||
const {
|
||||
external_id: externalId,
|
||||
id: userId,
|
||||
author_id: authorId,
|
||||
} = await makeTestUser(source, {
|
||||
account_id: tier5Accounts.account.id,
|
||||
external_id: 'author-user-external-id',
|
||||
role: 'author',
|
||||
author_id: 'AUTHOR_ID',
|
||||
});
|
||||
// 昨日の日付を作成
|
||||
let yesterday = new Date();
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
yesterday = new DateWithZeroTime(yesterday);
|
||||
// 期限切れのライセンスを作成して紐づける
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
yesterday,
|
||||
tier5Accounts.account.id,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
userId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
|
||||
|
||||
const { audioFileId } = await createTask(
|
||||
source,
|
||||
tier5Accounts.account.id,
|
||||
url,
|
||||
'test.zip',
|
||||
'InProgress',
|
||||
undefined,
|
||||
authorId ?? '',
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||
blobParam.fileExists = false;
|
||||
|
||||
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlobAndNotification(
|
||||
source,
|
||||
blobParam,
|
||||
notificationParam,
|
||||
);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
service.publishAudioFileDownloadSas(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
externalId,
|
||||
audioFileId,
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010805'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
@ -1596,70 +1338,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
);
|
||||
expect(resultUrl).toBe(`${url}?sas-token`);
|
||||
});
|
||||
it('ダウンロードSASトークンが乗っているURLを取得できる(第五階層の場合ライセンスのチェックを行う)', async () => {
|
||||
if (!source) fail();
|
||||
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する
|
||||
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
|
||||
source,
|
||||
);
|
||||
const tier5Accounts = await makeTestAccount(source, {
|
||||
parent_account_id: tier4Accounts[0].account.id,
|
||||
tier: 5,
|
||||
});
|
||||
const { external_id: externalId, id: userId } = await makeTestUser(source, {
|
||||
account_id: tier5Accounts.account.id,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: USER_ROLES.TYPIST,
|
||||
});
|
||||
// 本日の日付を作成
|
||||
let yesterday = new Date();
|
||||
yesterday.setDate(yesterday.getDate());
|
||||
yesterday = new DateWithZeroTime(yesterday);
|
||||
// 有効期限内のライセンスを作成して紐づける
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
yesterday,
|
||||
tier5Accounts.account.id,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
userId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
|
||||
|
||||
const { audioFileId } = await createTask(
|
||||
source,
|
||||
tier5Accounts.account.id,
|
||||
url,
|
||||
'test.zip',
|
||||
TASK_STATUS.IN_PROGRESS,
|
||||
userId,
|
||||
'AUTHOR_ID',
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlobAndNotification(
|
||||
source,
|
||||
blobParam,
|
||||
notificationParam,
|
||||
);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
const resultUrl = await service.publishTemplateFileDownloadSas(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
externalId,
|
||||
audioFileId,
|
||||
);
|
||||
expect(resultUrl).toBe(`${url}?sas-token`);
|
||||
});
|
||||
it('タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
@ -1849,135 +1528,6 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
}
|
||||
}
|
||||
});
|
||||
it('ダウンロード時にユーザーにライセンスが未割当の場合エラーとなる(第五階層限定)', async () => {
|
||||
if (!source) fail();
|
||||
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する(ライセンスは作成しない)
|
||||
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
|
||||
source,
|
||||
);
|
||||
const tier5Accounts = await makeTestAccount(source, {
|
||||
parent_account_id: tier4Accounts[0].account.id,
|
||||
tier: 5,
|
||||
});
|
||||
const { external_id: externalId, id: userId } = await makeTestUser(source, {
|
||||
account_id: tier5Accounts.account.id,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: USER_ROLES.TYPIST,
|
||||
});
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
|
||||
|
||||
const { audioFileId } = await createTask(
|
||||
source,
|
||||
tier5Accounts.account.id,
|
||||
url,
|
||||
'test.zip',
|
||||
TASK_STATUS.IN_PROGRESS,
|
||||
undefined,
|
||||
'AUTHOR_ID',
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||
blobParam.fileExists = false;
|
||||
|
||||
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlobAndNotification(
|
||||
source,
|
||||
blobParam,
|
||||
notificationParam,
|
||||
);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
try {
|
||||
await service.publishTemplateFileDownloadSas(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
externalId,
|
||||
audioFileId,
|
||||
);
|
||||
fail();
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E010812'));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
it('ダウンロード時にユーザーに割り当てられたライセンスが有効期限切れの場合エラー(第五階層限定)', async () => {
|
||||
if (!source) fail();
|
||||
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する
|
||||
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
|
||||
source,
|
||||
);
|
||||
const tier5Accounts = await makeTestAccount(source, {
|
||||
parent_account_id: tier4Accounts[0].account.id,
|
||||
tier: 5,
|
||||
});
|
||||
const { external_id: externalId, id: userId } = await makeTestUser(source, {
|
||||
account_id: tier5Accounts.account.id,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: USER_ROLES.TYPIST,
|
||||
});
|
||||
// 昨日の日付を作成
|
||||
let yesterday = new Date();
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
yesterday = new DateWithZeroTime(yesterday);
|
||||
// 期限切れのライセンスを作成して紐づける
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
yesterday,
|
||||
tier5Accounts.account.id,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
userId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
|
||||
|
||||
const { audioFileId } = await createTask(
|
||||
source,
|
||||
tier5Accounts.account.id,
|
||||
url,
|
||||
'test.zip',
|
||||
TASK_STATUS.IN_PROGRESS,
|
||||
undefined,
|
||||
'AUTHOR_ID',
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
blobParam.publishDownloadSas = `${url}?sas-token`;
|
||||
blobParam.fileExists = false;
|
||||
|
||||
const notificationParam = makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlobAndNotification(
|
||||
source,
|
||||
blobParam,
|
||||
notificationParam,
|
||||
);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
try {
|
||||
await service.publishTemplateFileDownloadSas(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
externalId,
|
||||
audioFileId,
|
||||
),
|
||||
fail();
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E010805'));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('publishTemplateFileUploadSas', () => {
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
OPTION_ITEM_NUM,
|
||||
TASK_STATUS,
|
||||
TIERS,
|
||||
USER_LICENSE_STATUS,
|
||||
USER_ROLES,
|
||||
} from '../../constants/index';
|
||||
import { User } from '../../repositories/users/entity/user.entity';
|
||||
@ -308,10 +309,10 @@ export class FilesService {
|
||||
context,
|
||||
user.id,
|
||||
);
|
||||
if (state === 'expired') {
|
||||
if (state === USER_LICENSE_STATUS.EXPIRED) {
|
||||
throw new LicenseExpiredError('license is expired.');
|
||||
}
|
||||
if (state === 'inallocated') {
|
||||
if (state === USER_LICENSE_STATUS.UNALLOCATED) {
|
||||
throw new LicenseNotAllocatedError('license is not allocated.');
|
||||
}
|
||||
}
|
||||
@ -392,20 +393,6 @@ export class FilesService {
|
||||
if (!user.account) {
|
||||
throw new AccountNotFoundError('account not found.');
|
||||
}
|
||||
// 第五階層のみチェック
|
||||
if (user.account.tier === TIERS.TIER5) {
|
||||
// ライセンスが有効でない場合、エラー
|
||||
const { state } = await this.licensesRepository.getLicenseState(
|
||||
context,
|
||||
user.id,
|
||||
);
|
||||
if (state === 'expired') {
|
||||
throw new LicenseExpiredError('license is expired.');
|
||||
}
|
||||
if (state === 'inallocated') {
|
||||
throw new LicenseNotAllocatedError('license is not allocated.');
|
||||
}
|
||||
}
|
||||
accountId = user.account.id;
|
||||
userId = user.id;
|
||||
country = user.account.country;
|
||||
@ -422,16 +409,6 @@ export class FilesService {
|
||||
}`,
|
||||
);
|
||||
switch (e.constructor) {
|
||||
case LicenseExpiredError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010805'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
case LicenseNotAllocatedError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010812'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
default:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
@ -571,20 +548,6 @@ export class FilesService {
|
||||
if (!user.account) {
|
||||
throw new AccountNotFoundError('account not found.');
|
||||
}
|
||||
// 第五階層のみチェック
|
||||
if (user.account.tier === TIERS.TIER5) {
|
||||
// ライセンスが有効でない場合、エラー
|
||||
const { state } = await this.licensesRepository.getLicenseState(
|
||||
context,
|
||||
user.id,
|
||||
);
|
||||
if (state === 'expired') {
|
||||
throw new LicenseExpiredError('license is expired.');
|
||||
}
|
||||
if (state === 'inallocated') {
|
||||
throw new LicenseNotAllocatedError('license is not allocated.');
|
||||
}
|
||||
}
|
||||
accountId = user.account_id;
|
||||
userId = user.id;
|
||||
country = user.account.country;
|
||||
@ -596,16 +559,6 @@ export class FilesService {
|
||||
}`,
|
||||
);
|
||||
switch (e.constructor) {
|
||||
case LicenseExpiredError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010805'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
case LicenseNotAllocatedError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010812'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
default:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
|
||||
@ -17,15 +17,15 @@ import {
|
||||
selectOrderLicense,
|
||||
} from './test/utility';
|
||||
import { UsersService } from '../users/users.service';
|
||||
import { makeContext } from '../../common/log';
|
||||
import { LICENSE_ALLOCATED_STATUS, LICENSE_TYPE } from '../../constants';
|
||||
import { Context, makeContext } from '../../common/log';
|
||||
import { ADB2C_SIGN_IN_TYPE, LICENSE_ALLOCATED_STATUS, LICENSE_TYPE } from '../../constants';
|
||||
import {
|
||||
makeHierarchicalAccounts,
|
||||
makeTestSimpleAccount,
|
||||
makeTestUser,
|
||||
} from '../../common/test/utility';
|
||||
import { LicensesRepositoryService } from '../../repositories/licenses/licenses.repository.service';
|
||||
import { overrideSendgridService } from '../../common/test/overrides';
|
||||
import { overrideAdB2cService, overrideSendgridService } from '../../common/test/overrides';
|
||||
import { truncateAllTable } from '../../common/test/init';
|
||||
|
||||
describe('ライセンス注文', () => {
|
||||
@ -192,8 +192,8 @@ describe('ライセンス注文', () => {
|
||||
await service.licenseOrders(context, externalId, poNumber, orderCount);
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
|
||||
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E010501'));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
@ -672,7 +672,18 @@ describe('ライセンス割り当て', () => {
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: dealerId } = await makeTestSimpleAccount(source, { company_name: "DEALER_COMPANY", tier: 4 });
|
||||
const { id: dealerAdminId } = await makeTestUser(source, {
|
||||
account_id: dealerId,
|
||||
external_id: 'userId_admin',
|
||||
role: 'admin',
|
||||
author_id: undefined,
|
||||
});
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source, {
|
||||
parent_account_id: dealerId,
|
||||
tier: 5
|
||||
});
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'userId',
|
||||
@ -701,7 +712,55 @@ describe('ライセンス割り当て', () => {
|
||||
);
|
||||
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
overrideSendgridService(service, {});
|
||||
let _subject: string = '';
|
||||
let _url: string | undefined = '';
|
||||
overrideAdB2cService(service, {
|
||||
getUser: async (context, externalId) => {
|
||||
return {
|
||||
displayName: 'TEMP' + externalId,
|
||||
id: externalId,
|
||||
identities: [
|
||||
{
|
||||
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
|
||||
issuer: 'xxxxxx',
|
||||
issuerAssignedId: 'mail@example.com',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
getUsers: async (context, externalIds) => {
|
||||
return externalIds.map((x) => ({
|
||||
displayName: 'admin',
|
||||
id: x,
|
||||
identities: [
|
||||
{
|
||||
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
|
||||
issuer: 'xxxxxx',
|
||||
issuerAssignedId: `mail+${x}@example.com`,
|
||||
},
|
||||
],
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
overrideSendgridService(service, {
|
||||
sendMail: async (
|
||||
context: Context,
|
||||
to: string[],
|
||||
cc: string[],
|
||||
from: string,
|
||||
subject: string,
|
||||
text: string,
|
||||
html: string,
|
||||
) => {
|
||||
const urlPattern = /https?:\/\/[^\s]+/g;
|
||||
const urls = text.match(urlPattern);
|
||||
const url = urls?.pop();
|
||||
|
||||
_subject = subject;
|
||||
_url = url;
|
||||
},
|
||||
});
|
||||
|
||||
const expiry_date = new NewAllocatedLicenseExpirationDate();
|
||||
|
||||
@ -735,6 +794,9 @@ describe('ライセンス割り当て', () => {
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
|
||||
accountId,
|
||||
);
|
||||
|
||||
expect(_subject).toBe('License Assigned Notification [U-108]');
|
||||
expect(_url).toBe('http://localhost:8081/');
|
||||
});
|
||||
|
||||
it('再割り当て可能なライセンスに対して、ライセンス割り当てが完了する', async () => {
|
||||
|
||||
@ -80,7 +80,9 @@ export class LicensesService {
|
||||
.parent_account_id ?? undefined;
|
||||
// 親アカウントIDが取得できない場合はエラー
|
||||
if (parentAccountId === undefined) {
|
||||
throw new Error('parent account id is undefined');
|
||||
throw new AccountNotFoundError(
|
||||
`parent account id is not found. myAccountId: ${myAccountId}`,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
@ -147,6 +149,7 @@ export class LicensesService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async issueCardLicenseKeys(
|
||||
context: Context,
|
||||
externalId: string,
|
||||
|
||||
@ -8,6 +8,7 @@ import { UserGroupsRepositoryModule } from '../../repositories/user_groups/user_
|
||||
import { NotificationhubModule } from '../../gateways/notificationhub/notificationhub.module';
|
||||
import { SendGridModule } from '../../gateways/sendgrid/sendgrid.module';
|
||||
import { AccountsRepositoryModule } from '../../repositories/accounts/accounts.repository.module';
|
||||
import { LicensesRepositoryModule } from '../../repositories/licenses/licenses.repository.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -18,6 +19,7 @@ import { AccountsRepositoryModule } from '../../repositories/accounts/accounts.r
|
||||
AdB2cModule,
|
||||
NotificationhubModule,
|
||||
SendGridModule,
|
||||
LicensesRepositoryModule,
|
||||
],
|
||||
providers: [TasksService],
|
||||
controllers: [TasksController],
|
||||
|
||||
@ -25,7 +25,13 @@ import {
|
||||
makeTestSimpleAccount,
|
||||
makeTestUser,
|
||||
} from '../../common/test/utility';
|
||||
import { ADMIN_ROLES, TASK_STATUS, USER_ROLES } from '../../constants';
|
||||
import {
|
||||
ADMIN_ROLES,
|
||||
LICENSE_ALLOCATED_STATUS,
|
||||
LICENSE_TYPE,
|
||||
TASK_STATUS,
|
||||
USER_ROLES,
|
||||
} from '../../constants';
|
||||
import { makeTestingModule } from '../../common/test/modules';
|
||||
import { createSortCriteria } from '../users/test/utility';
|
||||
import { createWorktype } from '../accounts/test/utility';
|
||||
@ -38,6 +44,9 @@ import { NotificationhubService } from '../../gateways/notificationhub/notificat
|
||||
import { Roles } from '../../common/types/role';
|
||||
import { TasksRepositoryService } from '../../repositories/tasks/tasks.repository.service';
|
||||
import { truncateAllTable } from '../../common/test/init';
|
||||
import { makeDefaultLicensesRepositoryMockValue } from '../accounts/test/accounts.service.mock';
|
||||
import { DateWithZeroTime } from '../licenses/types/types';
|
||||
import { createLicense } from '../licenses/test/utility';
|
||||
|
||||
describe('TasksService', () => {
|
||||
it('タスク一覧を取得できる(admin)', async () => {
|
||||
@ -48,12 +57,15 @@ describe('TasksService', () => {
|
||||
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
|
||||
const notificationhubServiceMockValue =
|
||||
makeDefaultNotificationhubServiceMockValue();
|
||||
const licensesRepositoryMockValue =
|
||||
makeDefaultLicensesRepositoryMockValue();
|
||||
const service = await makeTasksServiceMock(
|
||||
tasksRepositoryMockValue,
|
||||
usersRepositoryMockValue,
|
||||
userGroupsRepositoryMockValue,
|
||||
adb2cServiceMockValue,
|
||||
notificationhubServiceMockValue,
|
||||
licensesRepositoryMockValue,
|
||||
);
|
||||
|
||||
const userId = 'userId';
|
||||
@ -122,6 +134,8 @@ describe('TasksService', () => {
|
||||
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
|
||||
const notificationhubServiceMockValue =
|
||||
makeDefaultNotificationhubServiceMockValue();
|
||||
const licensesRepositoryMockValue =
|
||||
makeDefaultLicensesRepositoryMockValue();
|
||||
usersRepositoryMockValue.findUserByExternalId = new Error('DB failed');
|
||||
const service = await makeTasksServiceMock(
|
||||
tasksRepositoryMockValue,
|
||||
@ -129,6 +143,7 @@ describe('TasksService', () => {
|
||||
userGroupsRepositoryMockValue,
|
||||
adb2cServiceMockValue,
|
||||
notificationhubServiceMockValue,
|
||||
licensesRepositoryMockValue,
|
||||
);
|
||||
|
||||
const userId = 'userId';
|
||||
@ -164,6 +179,8 @@ describe('TasksService', () => {
|
||||
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
|
||||
const notificationhubServiceMockValue =
|
||||
makeDefaultNotificationhubServiceMockValue();
|
||||
const licensesRepositoryMockValue =
|
||||
makeDefaultLicensesRepositoryMockValue();
|
||||
tasksRepositoryMockValue.getTasksFromAccountId = new Error('DB failed');
|
||||
const service = await makeTasksServiceMock(
|
||||
tasksRepositoryMockValue,
|
||||
@ -171,6 +188,7 @@ describe('TasksService', () => {
|
||||
userGroupsRepositoryMockValue,
|
||||
adb2cServiceMockValue,
|
||||
notificationhubServiceMockValue,
|
||||
licensesRepositoryMockValue,
|
||||
);
|
||||
|
||||
const userId = 'userId';
|
||||
@ -252,12 +270,15 @@ describe('TasksService', () => {
|
||||
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
|
||||
const notificationhubServiceMockValue =
|
||||
makeDefaultNotificationhubServiceMockValue();
|
||||
const licensesRepositoryMockValue =
|
||||
makeDefaultLicensesRepositoryMockValue();
|
||||
const service = await makeTasksServiceMock(
|
||||
tasksRepositoryMockValue,
|
||||
usersRepositoryMockValue,
|
||||
userGroupsRepositoryMockValue,
|
||||
adb2cServiceMockValue,
|
||||
notificationhubServiceMockValue,
|
||||
licensesRepositoryMockValue,
|
||||
);
|
||||
const userId = 'userId';
|
||||
const offset = 0;
|
||||
@ -292,6 +313,8 @@ describe('TasksService', () => {
|
||||
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
|
||||
const notificationhubServiceMockValue =
|
||||
makeDefaultNotificationhubServiceMockValue();
|
||||
const licensesRepositoryMockValue =
|
||||
makeDefaultLicensesRepositoryMockValue();
|
||||
if (usersRepositoryMockValue.findUserByExternalId instanceof Error) {
|
||||
return;
|
||||
}
|
||||
@ -302,6 +325,7 @@ describe('TasksService', () => {
|
||||
userGroupsRepositoryMockValue,
|
||||
adb2cServiceMockValue,
|
||||
notificationhubServiceMockValue,
|
||||
licensesRepositoryMockValue,
|
||||
);
|
||||
|
||||
const userId = 'userId';
|
||||
@ -376,6 +400,8 @@ describe('TasksService', () => {
|
||||
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
|
||||
const notificationhubServiceMockValue =
|
||||
makeDefaultNotificationhubServiceMockValue();
|
||||
const licensesRepositoryMockValue =
|
||||
makeDefaultLicensesRepositoryMockValue();
|
||||
tasksRepositoryMockValue.getTasksFromAuthorIdAndAccountId = new Error(
|
||||
'DB failed',
|
||||
);
|
||||
@ -385,6 +411,7 @@ describe('TasksService', () => {
|
||||
userGroupsRepositoryMockValue,
|
||||
adb2cServiceMockValue,
|
||||
notificationhubServiceMockValue,
|
||||
licensesRepositoryMockValue,
|
||||
);
|
||||
|
||||
const userId = 'userId';
|
||||
@ -420,6 +447,8 @@ describe('TasksService', () => {
|
||||
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
|
||||
const notificationhubServiceMockValue =
|
||||
makeDefaultNotificationhubServiceMockValue();
|
||||
const licensesRepositoryMockValue =
|
||||
makeDefaultLicensesRepositoryMockValue();
|
||||
if (usersRepositoryMockValue.findUserByExternalId instanceof Error) {
|
||||
return;
|
||||
}
|
||||
@ -431,6 +460,7 @@ describe('TasksService', () => {
|
||||
userGroupsRepositoryMockValue,
|
||||
adb2cServiceMockValue,
|
||||
notificationhubServiceMockValue,
|
||||
licensesRepositoryMockValue,
|
||||
);
|
||||
|
||||
const userId = 'userId';
|
||||
@ -508,12 +538,15 @@ describe('TasksService', () => {
|
||||
tasksRepositoryMockValue.getTasksFromTypistRelations = new Error(
|
||||
'DB failed',
|
||||
);
|
||||
const licensesRepositoryMockValue =
|
||||
makeDefaultLicensesRepositoryMockValue();
|
||||
const service = await makeTasksServiceMock(
|
||||
tasksRepositoryMockValue,
|
||||
usersRepositoryMockValue,
|
||||
userGroupsRepositoryMockValue,
|
||||
adb2cServiceMockValue,
|
||||
notificationhubServiceMockValue,
|
||||
licensesRepositoryMockValue,
|
||||
);
|
||||
|
||||
const userId = 'userId';
|
||||
@ -549,6 +582,8 @@ describe('TasksService', () => {
|
||||
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
|
||||
const notificationhubServiceMockValue =
|
||||
makeDefaultNotificationhubServiceMockValue();
|
||||
const licensesRepositoryMockValue =
|
||||
makeDefaultLicensesRepositoryMockValue();
|
||||
adb2cServiceMockValue.getUsers = new Adb2cTooManyRequestsError();
|
||||
const service = await makeTasksServiceMock(
|
||||
tasksRepositoryMockValue,
|
||||
@ -556,6 +591,7 @@ describe('TasksService', () => {
|
||||
userGroupsRepositoryMockValue,
|
||||
adb2cServiceMockValue,
|
||||
notificationhubServiceMockValue,
|
||||
licensesRepositoryMockValue,
|
||||
);
|
||||
|
||||
const userId = 'userId';
|
||||
@ -1632,7 +1668,75 @@ describe('checkout', () => {
|
||||
user_group_id: null,
|
||||
});
|
||||
});
|
||||
it('第五階層のアカウントの場合、有効なライセンスが割当されている場合チェックアウトできる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
// 第五階層のアカウントを作成
|
||||
const { id: accountId } = await makeTestSimpleAccount(source, { tier: 5 });
|
||||
const { id: typistUserId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: 'typist',
|
||||
});
|
||||
const { id: authorUserId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'author-user-external-id',
|
||||
role: 'author',
|
||||
author_id: 'MY_AUTHOR_ID',
|
||||
});
|
||||
// 本日の日付を作成
|
||||
const today = new Date();
|
||||
// 有効なライセンスを作成して紐づける
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
today,
|
||||
accountId,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
typistUserId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
const { taskId } = await createTask(
|
||||
source,
|
||||
accountId,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'',
|
||||
'01',
|
||||
'00000001',
|
||||
'Pending',
|
||||
);
|
||||
await createCheckoutPermissions(source, taskId, typistUserId);
|
||||
|
||||
const service = module.get<TasksService>(TasksService);
|
||||
|
||||
const initTask = await getTask(source, taskId);
|
||||
|
||||
await service.checkout(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
1,
|
||||
['typist'],
|
||||
'typist-user-external-id',
|
||||
);
|
||||
const resultTask = await getTask(source, taskId);
|
||||
const permisions = await getCheckoutPermissions(source, taskId);
|
||||
|
||||
expect(resultTask?.status).toEqual('InProgress');
|
||||
expect(resultTask?.typist_user_id).toEqual(typistUserId);
|
||||
//タスクの元々のステータスがPending,Inprogressの場合、文字起こし開始時刻は更新されない
|
||||
expect(resultTask?.started_at).toEqual(initTask?.started_at);
|
||||
expect(permisions.length).toEqual(1);
|
||||
expect(permisions[0]).toEqual({
|
||||
id: 2,
|
||||
task_id: 1,
|
||||
user_id: 1,
|
||||
user_group_id: null,
|
||||
});
|
||||
});
|
||||
it('ユーザーのRoleがTypistで、対象のタスクのStatus[Uploaded,Inprogress,Pending]以外の時、タスクをチェックアウトできない', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
@ -1678,7 +1782,116 @@ describe('checkout', () => {
|
||||
}
|
||||
}
|
||||
});
|
||||
it('第五階層のアカウントの場合、ライセンスが未割当の場合チェックアウトできない', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
// 第五階層のアカウントを作成
|
||||
const { id: accountId } = await makeTestSimpleAccount(source, { tier: 5 });
|
||||
await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: 'typist',
|
||||
});
|
||||
const { id: authorUserId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'author-user-external-id',
|
||||
role: 'author',
|
||||
author_id: 'MY_AUTHOR_ID',
|
||||
});
|
||||
await createTask(
|
||||
source,
|
||||
accountId,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'',
|
||||
'01',
|
||||
'00000001',
|
||||
'Backup',
|
||||
);
|
||||
|
||||
const service = module.get<TasksService>(TasksService);
|
||||
try {
|
||||
await service.checkout(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
1,
|
||||
['typist'],
|
||||
'typist-user-external-id',
|
||||
);
|
||||
fail();
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E010812'));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
it('第五階層のアカウントの場合、ライセンスが有効期限切れの場合チェックアウトできない', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
// 第五階層のアカウントを作成
|
||||
const { id: accountId } = await makeTestSimpleAccount(source, { tier: 5 });
|
||||
const { id: typistUserId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: 'typist',
|
||||
});
|
||||
const { id: authorUserId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'author-user-external-id',
|
||||
role: 'author',
|
||||
author_id: 'MY_AUTHOR_ID',
|
||||
});
|
||||
// 昨日の日付を作成
|
||||
let yesterday = new Date();
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
yesterday = new DateWithZeroTime(yesterday);
|
||||
// 期限切れのライセンスを作成して紐づける
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
yesterday,
|
||||
accountId,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
typistUserId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
await createTask(
|
||||
source,
|
||||
accountId,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'',
|
||||
'01',
|
||||
'00000001',
|
||||
'Backup',
|
||||
);
|
||||
|
||||
const service = module.get<TasksService>(TasksService);
|
||||
try {
|
||||
await service.checkout(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
1,
|
||||
['typist'],
|
||||
'typist-user-external-id',
|
||||
);
|
||||
fail();
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E010805'));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
it('ユーザーのRoleがTypistで、チェックアウト権限が存在しない時、タスクをチェックアウトできない', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
@ -9,7 +9,13 @@ import {
|
||||
SortDirection,
|
||||
TaskListSortableAttribute,
|
||||
} from '../../common/types/sort';
|
||||
import { ADMIN_ROLES, TASK_STATUS, USER_ROLES } from '../../constants';
|
||||
import {
|
||||
ADMIN_ROLES,
|
||||
TASK_STATUS,
|
||||
TIERS,
|
||||
USER_LICENSE_STATUS,
|
||||
USER_ROLES,
|
||||
} from '../../constants';
|
||||
import {
|
||||
AdB2cService,
|
||||
Adb2cTooManyRequestsError,
|
||||
@ -36,6 +42,12 @@ 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';
|
||||
import { AccountNotFoundError } from '../../repositories/accounts/errors/types';
|
||||
import {
|
||||
LicenseExpiredError,
|
||||
LicenseNotAllocatedError,
|
||||
} from '../../repositories/licenses/errors/types';
|
||||
import { LicensesRepositoryService } from '../../repositories/licenses/licenses.repository.service';
|
||||
|
||||
@Injectable()
|
||||
export class TasksService {
|
||||
@ -48,6 +60,7 @@ export class TasksService {
|
||||
private readonly adB2cService: AdB2cService,
|
||||
private readonly sendgridService: SendGridService,
|
||||
private readonly notificationhubService: NotificationhubService,
|
||||
private readonly licensesRepository: LicensesRepositoryService,
|
||||
) {}
|
||||
|
||||
async getTasks(
|
||||
@ -276,9 +289,26 @@ export class TasksService {
|
||||
} | params: { audioFileId: ${audioFileId}, roles: ${roles}, externalId: ${externalId} };`,
|
||||
);
|
||||
|
||||
const { id, account_id, author_id } =
|
||||
const { id, account_id, author_id, account } =
|
||||
await this.usersRepository.findUserByExternalId(context, externalId);
|
||||
|
||||
if (!account) {
|
||||
throw new AccountNotFoundError('account not found.');
|
||||
}
|
||||
// 第五階層のみチェック
|
||||
if (account.tier === TIERS.TIER5) {
|
||||
// ライセンスが有効でない場合、エラー
|
||||
const { state } = await this.licensesRepository.getLicenseState(
|
||||
context,
|
||||
id,
|
||||
);
|
||||
if (state === USER_LICENSE_STATUS.EXPIRED) {
|
||||
throw new LicenseExpiredError('license is expired.');
|
||||
}
|
||||
if (state === USER_LICENSE_STATUS.UNALLOCATED) {
|
||||
throw new LicenseNotAllocatedError('license is not allocated.');
|
||||
}
|
||||
}
|
||||
if (roles.includes(USER_ROLES.AUTHOR)) {
|
||||
// API実行者がAuthorで、AuthorIDが存在しないことは想定外のため、エラーとする
|
||||
if (!author_id) {
|
||||
@ -308,6 +338,16 @@ export class TasksService {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
if (e instanceof Error) {
|
||||
switch (e.constructor) {
|
||||
case LicenseExpiredError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010805'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
case LicenseNotAllocatedError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010812'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
case CheckoutPermissionNotFoundError:
|
||||
case TaskAuthorIdNotMatchError:
|
||||
case InvalidRoleError:
|
||||
|
||||
@ -17,6 +17,11 @@ import { NotificationhubService } from '../../../gateways/notificationhub/notifi
|
||||
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';
|
||||
import {
|
||||
LicensesRepositoryMockValue,
|
||||
makeLicensesRepositoryMock,
|
||||
} from '../../accounts/test/accounts.service.mock';
|
||||
import { LicensesRepositoryService } from '../../../repositories/licenses/licenses.repository.service';
|
||||
|
||||
export type TasksRepositoryMockValue = {
|
||||
getTasksFromAccountId:
|
||||
@ -65,6 +70,7 @@ export const makeTasksServiceMock = async (
|
||||
userGroupsRepositoryMockValue: UserGroupsRepositoryMockValue,
|
||||
adB2CServiceMockValue: AdB2CServiceMockValue,
|
||||
notificationhubServiceMockValue: NotificationhubServiceMockValue,
|
||||
licensesRepositoryMockValue: LicensesRepositoryMockValue,
|
||||
): Promise<{
|
||||
tasksService: TasksService;
|
||||
taskRepoService: TasksRepositoryService;
|
||||
@ -92,6 +98,8 @@ export const makeTasksServiceMock = async (
|
||||
// メール送信でしか利用しておらず、テストする必要がないが、依存関係解決のため空オブジェクトを定義しておく。
|
||||
case SendGridService:
|
||||
return {};
|
||||
case LicensesRepositoryService:
|
||||
return makeLicensesRepositoryMock(licensesRepositoryMockValue);
|
||||
}
|
||||
})
|
||||
.compile();
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
} from 'class-validator';
|
||||
import {
|
||||
TASK_LIST_SORTABLE_ATTRIBUTES,
|
||||
USER_LICENSE_STATUS,
|
||||
USER_LICENSE_EXPIRY_STATUS,
|
||||
} from '../../../constants';
|
||||
import { USER_ROLES } from '../../../constants';
|
||||
import {
|
||||
@ -67,9 +67,9 @@ export class User {
|
||||
remaining?: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: `${Object.values(USER_LICENSE_STATUS).join('/')}`,
|
||||
description: `${Object.values(USER_LICENSE_EXPIRY_STATUS).join('/')}`,
|
||||
})
|
||||
@IsIn(Object.values(USER_LICENSE_STATUS), {
|
||||
@IsIn(Object.values(USER_LICENSE_EXPIRY_STATUS), {
|
||||
message: 'invalid license status',
|
||||
})
|
||||
licenseStatus: string;
|
||||
|
||||
@ -22,7 +22,7 @@ import {
|
||||
LICENSE_EXPIRATION_THRESHOLD_DAYS,
|
||||
LICENSE_TYPE,
|
||||
USER_AUDIO_FORMAT,
|
||||
USER_LICENSE_STATUS,
|
||||
USER_LICENSE_EXPIRY_STATUS,
|
||||
USER_ROLES,
|
||||
} from '../../constants';
|
||||
import { makeTestingModule } from '../../common/test/modules';
|
||||
@ -108,7 +108,26 @@ describe('UsersService.confirmUser', () => {
|
||||
});
|
||||
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
overrideSendgridService(service, {});
|
||||
let _subject: string = '';
|
||||
let _url: string | undefined = '';
|
||||
overrideSendgridService(service, {
|
||||
sendMail: async (
|
||||
context: Context,
|
||||
to: string[],
|
||||
cc: string[],
|
||||
from: string,
|
||||
subject: string,
|
||||
text: string,
|
||||
html: string,
|
||||
) => {
|
||||
const urlPattern = /https?:\/\/[^\s]+/g;
|
||||
const urls = text.match(urlPattern);
|
||||
const url = urls?.pop();
|
||||
|
||||
_subject = subject;
|
||||
_url = url;
|
||||
},
|
||||
});
|
||||
|
||||
// account id:1, user id: 2のトークン
|
||||
const token =
|
||||
@ -149,6 +168,8 @@ describe('UsersService.confirmUser', () => {
|
||||
delete_order_id: null,
|
||||
user: null,
|
||||
});
|
||||
expect(_subject).toBe('Account Registered Notification [U-101]');
|
||||
expect(_url).toBe('http://localhost:8081/');
|
||||
}, 600000);
|
||||
|
||||
it('トークンの形式が不正な場合、形式不正エラーとなる。', async () => {
|
||||
@ -506,7 +527,26 @@ describe('UsersService.createUser', () => {
|
||||
};
|
||||
},
|
||||
});
|
||||
overrideSendgridService(service, {});
|
||||
let _subject: string = '';
|
||||
let _url: string | undefined = '';
|
||||
overrideSendgridService(service, {
|
||||
sendMail: async (
|
||||
context: Context,
|
||||
to: string[],
|
||||
cc: string[],
|
||||
from: string,
|
||||
subject: string,
|
||||
text: string,
|
||||
html: string,
|
||||
) => {
|
||||
const urlPattern = /https?:\/\/[^\s]+/g;
|
||||
const urls = text.match(urlPattern);
|
||||
const url = urls?.pop();
|
||||
|
||||
_subject = subject;
|
||||
_url = url;
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
await service.createUser(
|
||||
@ -536,6 +576,11 @@ describe('UsersService.createUser', () => {
|
||||
// 他にユーザーが登録されていないことを確認
|
||||
const users = await getUsers(source);
|
||||
expect(users.length).toEqual(2);
|
||||
|
||||
expect(_subject).toBe('User Registration Notification [U-114]');
|
||||
expect(
|
||||
_url?.startsWith('http://localhost:8081/mail-confirm/user?verify='),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:Author; 暗号化あり)', async () => {
|
||||
@ -1479,7 +1524,7 @@ describe('UsersService.getUsers', () => {
|
||||
prompt: false,
|
||||
expiration: undefined,
|
||||
remaining: undefined,
|
||||
licenseStatus: USER_LICENSE_STATUS.NO_LICENSE,
|
||||
licenseStatus: USER_LICENSE_EXPIRY_STATUS.NO_LICENSE,
|
||||
},
|
||||
{
|
||||
id: typistUserId,
|
||||
@ -1495,7 +1540,7 @@ describe('UsersService.getUsers', () => {
|
||||
prompt: false,
|
||||
expiration: undefined,
|
||||
remaining: undefined,
|
||||
licenseStatus: USER_LICENSE_STATUS.NO_LICENSE,
|
||||
licenseStatus: USER_LICENSE_EXPIRY_STATUS.NO_LICENSE,
|
||||
},
|
||||
{
|
||||
id: noneUserId,
|
||||
@ -1511,7 +1556,7 @@ describe('UsersService.getUsers', () => {
|
||||
prompt: false,
|
||||
expiration: undefined,
|
||||
remaining: undefined,
|
||||
licenseStatus: USER_LICENSE_STATUS.NO_LICENSE,
|
||||
licenseStatus: USER_LICENSE_EXPIRY_STATUS.NO_LICENSE,
|
||||
},
|
||||
];
|
||||
|
||||
@ -1591,7 +1636,7 @@ describe('UsersService.getUsers', () => {
|
||||
date1.getMonth() + 1
|
||||
}/${date1.getDate()}`,
|
||||
remaining: LICENSE_EXPIRATION_THRESHOLD_DAYS + 1,
|
||||
licenseStatus: USER_LICENSE_STATUS.NORMAL,
|
||||
licenseStatus: USER_LICENSE_EXPIRY_STATUS.NORMAL,
|
||||
},
|
||||
{
|
||||
id: user2,
|
||||
@ -1609,7 +1654,7 @@ describe('UsersService.getUsers', () => {
|
||||
date2.getMonth() + 1
|
||||
}/${date2.getDate()}`,
|
||||
remaining: LICENSE_EXPIRATION_THRESHOLD_DAYS,
|
||||
licenseStatus: USER_LICENSE_STATUS.RENEW,
|
||||
licenseStatus: USER_LICENSE_EXPIRY_STATUS.RENEW,
|
||||
},
|
||||
{
|
||||
id: user3,
|
||||
@ -1627,7 +1672,7 @@ describe('UsersService.getUsers', () => {
|
||||
date3.getMonth() + 1
|
||||
}/${date3.getDate()}`,
|
||||
remaining: LICENSE_EXPIRATION_THRESHOLD_DAYS - 1,
|
||||
licenseStatus: USER_LICENSE_STATUS.ALERT,
|
||||
licenseStatus: USER_LICENSE_EXPIRY_STATUS.ALERT,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@ -37,7 +37,7 @@ import {
|
||||
MANUAL_RECOVERY_REQUIRED,
|
||||
OPTION_ITEM_VALUE_TYPE_NUMBER,
|
||||
USER_AUDIO_FORMAT,
|
||||
USER_LICENSE_STATUS,
|
||||
USER_LICENSE_EXPIRY_STATUS,
|
||||
USER_ROLES,
|
||||
} from '../../constants';
|
||||
import { DateWithZeroTime } from '../licenses/types/types';
|
||||
@ -617,7 +617,7 @@ export class UsersService {
|
||||
throw new Error('mail not found.');
|
||||
}
|
||||
|
||||
let status = USER_LICENSE_STATUS.NORMAL;
|
||||
let status = USER_LICENSE_EXPIRY_STATUS.NORMAL;
|
||||
|
||||
// ライセンスの有効期限と残日数は、ライセンスが存在する場合のみ算出する
|
||||
// ライセンスが存在しない場合は、undefinedのままとする
|
||||
@ -648,11 +648,11 @@ export class UsersService {
|
||||
remaining <= LICENSE_EXPIRATION_THRESHOLD_DAYS
|
||||
) {
|
||||
status = dbUser.auto_renew
|
||||
? USER_LICENSE_STATUS.RENEW
|
||||
: USER_LICENSE_STATUS.ALERT;
|
||||
? USER_LICENSE_EXPIRY_STATUS.RENEW
|
||||
: USER_LICENSE_EXPIRY_STATUS.ALERT;
|
||||
}
|
||||
} else {
|
||||
status = USER_LICENSE_STATUS.NO_LICENSE;
|
||||
status = USER_LICENSE_EXPIRY_STATUS.NO_LICENSE;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@ -21,6 +21,7 @@ import {
|
||||
VERIFY_LINK,
|
||||
TEMPORARY_PASSWORD,
|
||||
} from '../../templates/constants';
|
||||
import { URL } from 'node:url';
|
||||
|
||||
@Injectable()
|
||||
export class SendGridService {
|
||||
@ -204,12 +205,13 @@ export class SendGridService {
|
||||
);
|
||||
try {
|
||||
const subject = 'Account Registered Notification [U-101]';
|
||||
const url = new URL(this.appDomain).href;
|
||||
const html = this.templateU101Html
|
||||
.replaceAll(CUSTOMER_NAME, customerAccountName)
|
||||
.replaceAll(TOP_URL, this.appDomain);
|
||||
.replaceAll(TOP_URL, url);
|
||||
const text = this.templateU101Text
|
||||
.replaceAll(CUSTOMER_NAME, customerAccountName)
|
||||
.replaceAll(TOP_URL, this.appDomain);
|
||||
.replaceAll(TOP_URL, url);
|
||||
|
||||
await this.sendMail(
|
||||
context,
|
||||
@ -255,8 +257,9 @@ export class SendGridService {
|
||||
this.emailConfirmLifetime,
|
||||
privateKey,
|
||||
);
|
||||
const path = 'mail-confirm/';
|
||||
const verifyUrl = `${this.appDomain}${path}?verify=${token}`;
|
||||
const paths = path.join('mail-confirm');
|
||||
const url = new URL(paths, this.appDomain).href;
|
||||
const verifyUrl = `${url}?verify=${token}`;
|
||||
|
||||
const subject = 'User Registration Notification [U-102]';
|
||||
const html = this.templateU102Html.replaceAll(VERIFY_LINK, verifyUrl);
|
||||
@ -466,6 +469,7 @@ export class SendGridService {
|
||||
);
|
||||
try {
|
||||
const subject = 'License Assigned Notification [U-108]';
|
||||
const url = new URL(this.appDomain).href;
|
||||
|
||||
// メールの本文を作成する
|
||||
const html = this.templateU108Html
|
||||
@ -473,13 +477,13 @@ export class SendGridService {
|
||||
.replaceAll(DEALER_NAME, dealerAccountName)
|
||||
.replaceAll(USER_NAME, userName)
|
||||
.replaceAll(USER_EMAIL, userMail)
|
||||
.replaceAll(TOP_URL, this.appDomain);
|
||||
.replaceAll(TOP_URL, url);
|
||||
const text = this.templateU108Text
|
||||
.replaceAll(CUSTOMER_NAME, customerAccountName)
|
||||
.replaceAll(DEALER_NAME, dealerAccountName)
|
||||
.replaceAll(USER_NAME, userName)
|
||||
.replaceAll(USER_EMAIL, userMail)
|
||||
.replaceAll(TOP_URL, this.appDomain);
|
||||
.replaceAll(TOP_URL, url);
|
||||
|
||||
const ccAddress = customerAdminMails.includes(userMail) ? [] : [userMail];
|
||||
|
||||
@ -574,17 +578,18 @@ export class SendGridService {
|
||||
);
|
||||
try {
|
||||
const subject = 'Account Deleted Notification [U-111]';
|
||||
const url = new URL(this.appDomain).href;
|
||||
|
||||
// メールの本文を作成する
|
||||
const html = this.templateU111Html
|
||||
.replaceAll(CUSTOMER_NAME, customerAccountName)
|
||||
.replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
|
||||
.replaceAll(TOP_URL, this.appDomain);
|
||||
.replaceAll(TOP_URL, url);
|
||||
|
||||
const text = this.templateU111Text
|
||||
.replaceAll(CUSTOMER_NAME, customerAccountName)
|
||||
.replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
|
||||
.replaceAll(TOP_URL, this.appDomain);
|
||||
.replaceAll(TOP_URL, url);
|
||||
|
||||
// メールを送信する
|
||||
await this.sendMail(
|
||||
@ -627,6 +632,7 @@ export class SendGridService {
|
||||
|
||||
let html: string;
|
||||
let text: string;
|
||||
const url = new URL(this.appDomain).href;
|
||||
|
||||
// 親アカウントがない場合は別のテンプレートを使用する
|
||||
if (dealerAccountName === null) {
|
||||
@ -634,22 +640,22 @@ export class SendGridService {
|
||||
html = this.templateU112NoParentHtml
|
||||
.replaceAll(CUSTOMER_NAME, customerAccountName)
|
||||
.replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
|
||||
.replaceAll(TOP_URL, this.appDomain);
|
||||
.replaceAll(TOP_URL, url);
|
||||
text = this.templateU112NoParentText
|
||||
.replaceAll(CUSTOMER_NAME, customerAccountName)
|
||||
.replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
|
||||
.replaceAll(TOP_URL, this.appDomain);
|
||||
.replaceAll(TOP_URL, url);
|
||||
} else {
|
||||
html = this.templateU112Html
|
||||
.replaceAll(CUSTOMER_NAME, customerAccountName)
|
||||
.replaceAll(DEALER_NAME, dealerAccountName)
|
||||
.replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
|
||||
.replaceAll(TOP_URL, this.appDomain);
|
||||
.replaceAll(TOP_URL, url);
|
||||
text = this.templateU112Text
|
||||
.replaceAll(CUSTOMER_NAME, customerAccountName)
|
||||
.replaceAll(DEALER_NAME, dealerAccountName)
|
||||
.replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
|
||||
.replaceAll(TOP_URL, this.appDomain);
|
||||
.replaceAll(TOP_URL, url);
|
||||
}
|
||||
|
||||
// メールを送信する
|
||||
@ -745,18 +751,20 @@ export class SendGridService {
|
||||
this.emailConfirmLifetime,
|
||||
privateKey,
|
||||
);
|
||||
const path = 'mail-confirm/user/';
|
||||
const verifyLink = `${this.appDomain}${path}?verify=${token}`;
|
||||
|
||||
const paths = path.join('mail-confirm', '/user');
|
||||
const url = new URL(paths, this.appDomain);
|
||||
const verifyUrl = `${url}?verify=${token}`;
|
||||
|
||||
const subject = 'User Registration Notification [U-114]';
|
||||
|
||||
// メールの本文を作成する
|
||||
const html = this.templateU114Html
|
||||
.replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
|
||||
.replaceAll(VERIFY_LINK, verifyLink);
|
||||
.replaceAll(VERIFY_LINK, verifyUrl);
|
||||
const text = this.templateU114Text
|
||||
.replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
|
||||
.replaceAll(VERIFY_LINK, verifyLink);
|
||||
.replaceAll(VERIFY_LINK, verifyUrl);
|
||||
|
||||
// メールを送信する
|
||||
await this.sendMail(
|
||||
|
||||
@ -821,6 +821,7 @@ export class AccountsRepositoryService {
|
||||
status: Not(LICENSE_ALLOCATED_STATUS.UNALLOCATED),
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
// 存在した場合エラー
|
||||
@ -1023,6 +1024,7 @@ export class AccountsRepositoryService {
|
||||
email_verified: true,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (!primaryAdminUser) {
|
||||
throw new AdminUserNotFoundError(
|
||||
@ -1040,6 +1042,7 @@ export class AccountsRepositoryService {
|
||||
email_verified: true,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (!secondryAdminUser) {
|
||||
throw new AdminUserNotFoundError(
|
||||
|
||||
@ -12,9 +12,9 @@ import {
|
||||
LICENSE_ALLOCATED_STATUS,
|
||||
LICENSE_ISSUE_STATUS,
|
||||
LICENSE_TYPE,
|
||||
NODE_ENV_TEST,
|
||||
SWITCH_FROM_TYPE,
|
||||
TIERS,
|
||||
USER_LICENSE_STATUS,
|
||||
} from '../../constants';
|
||||
import {
|
||||
PoNumberAlreadyExistError,
|
||||
@ -39,6 +39,8 @@ import {
|
||||
updateEntity,
|
||||
} from '../../common/repository';
|
||||
import { Context } from '../../common/log';
|
||||
import { User } from '../users/entity/user.entity';
|
||||
import { UserNotFoundError } from '../users/errors/types';
|
||||
|
||||
@Injectable()
|
||||
export class LicensesRepositoryService {
|
||||
@ -81,6 +83,7 @@ export class LicensesRepositoryService {
|
||||
},
|
||||
],
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
// 重複があった場合はエラーを返却する
|
||||
if (isPoNumberDuplicated) {
|
||||
@ -193,6 +196,7 @@ export class LicensesRepositoryService {
|
||||
card_license_key: In(generateKeys),
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (existingCardLicenses.length > 0) {
|
||||
// 重複分を配列から削除
|
||||
@ -292,6 +296,7 @@ export class LicensesRepositoryService {
|
||||
card_license_key: licenseKey,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
// カードライセンスが存在しなければエラー
|
||||
if (!targetCardLicense) {
|
||||
@ -422,10 +427,7 @@ export class LicensesRepositoryService {
|
||||
po_number: poNumber,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
// テスト環境の場合はロックを行わない(sqliteがlockに対応していないため)
|
||||
...(process.env.NODE_ENV !== NODE_ENV_TEST
|
||||
? { lock: { mode: 'pessimistic_write' } }
|
||||
: {}),
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (!issuingOrder) {
|
||||
// 注文が存在しない場合、エラー
|
||||
@ -559,6 +561,19 @@ export class LicensesRepositoryService {
|
||||
accountId: number,
|
||||
): Promise<void> {
|
||||
await this.dataSource.transaction(async (entityManager) => {
|
||||
// 対象ユーザの存在チェック
|
||||
const userRepo = entityManager.getRepository(User);
|
||||
const user = await userRepo.findOne({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (!user) {
|
||||
throw new UserNotFoundError(`User not exist. userId: ${userId}`);
|
||||
}
|
||||
|
||||
const licenseRepo = entityManager.getRepository(License);
|
||||
const licenseAllocationHistoryRepo = entityManager.getRepository(
|
||||
LicenseAllocationHistory,
|
||||
@ -569,6 +584,7 @@ export class LicensesRepositoryService {
|
||||
id: newLicenseId,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
// ライセンスが存在しない場合はエラー
|
||||
@ -604,6 +620,7 @@ export class LicensesRepositoryService {
|
||||
allocated_user_id: userId,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
// 既にライセンスが割り当てられているなら、割り当てを解除
|
||||
@ -719,6 +736,7 @@ export class LicensesRepositoryService {
|
||||
status: LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
// ライセンスが割り当てられていない場合はエラー
|
||||
@ -778,6 +796,7 @@ export class LicensesRepositoryService {
|
||||
status: LICENSE_ISSUE_STATUS.ISSUE_REQUESTING,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
// キャンセル対象の注文が存在しない場合エラー
|
||||
@ -806,12 +825,17 @@ export class LicensesRepositoryService {
|
||||
* ライセンスの割当状態を取得します
|
||||
* @param userId ユーザーID
|
||||
* @error { Error } DBアクセス失敗時の例外
|
||||
* @returns Promise<{ state: 'allocated' | 'inallocated' | 'expired' }>
|
||||
* @returns Promise<{ state: 'allocated' | 'unallocated' | 'expired' }>
|
||||
*/
|
||||
async getLicenseState(
|
||||
context: Context,
|
||||
userId: number,
|
||||
): Promise<{ state: 'allocated' | 'inallocated' | 'expired' }> {
|
||||
): Promise<{
|
||||
state:
|
||||
| typeof USER_LICENSE_STATUS.ALLOCATED
|
||||
| typeof USER_LICENSE_STATUS.UNALLOCATED
|
||||
| typeof USER_LICENSE_STATUS.EXPIRED;
|
||||
}> {
|
||||
const allocatedLicense = await this.dataSource
|
||||
.getRepository(License)
|
||||
.findOne({
|
||||
@ -824,7 +848,7 @@ export class LicensesRepositoryService {
|
||||
|
||||
// ライセンスが割り当てられていない場合は未割当状態
|
||||
if (allocatedLicense == null) {
|
||||
return { state: 'inallocated' };
|
||||
return { state: USER_LICENSE_STATUS.UNALLOCATED };
|
||||
}
|
||||
|
||||
// ライセンスの有効期限が過ぎている場合は期限切れ状態
|
||||
@ -833,9 +857,9 @@ export class LicensesRepositoryService {
|
||||
allocatedLicense.expiry_date &&
|
||||
allocatedLicense.expiry_date < currentDate
|
||||
) {
|
||||
return { state: 'expired' };
|
||||
return { state: USER_LICENSE_STATUS.EXPIRED };
|
||||
}
|
||||
|
||||
return { state: 'allocated' };
|
||||
return { state: USER_LICENSE_STATUS.ALLOCATED };
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,6 +48,7 @@ import {
|
||||
deleteEntity,
|
||||
} from '../../common/repository';
|
||||
import { Context } from '../../common/log';
|
||||
import { UserNotFoundError } from '../users/errors/types';
|
||||
|
||||
@Injectable()
|
||||
export class TasksRepositoryService {
|
||||
@ -167,6 +168,20 @@ export class TasksRepositoryService {
|
||||
permittedSourceStatus: TaskStatus[],
|
||||
): Promise<void> {
|
||||
await this.dataSource.transaction(async (entityManager) => {
|
||||
// 対象ユーザの存在確認
|
||||
const userRepo = entityManager.getRepository(User);
|
||||
const user = await userRepo.findOne({
|
||||
where: {
|
||||
id: user_id,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (!user) {
|
||||
throw new TypistUserNotFoundError(
|
||||
`Typist user not exists. user_id:${user_id}`,
|
||||
);
|
||||
}
|
||||
const taskRepo = entityManager.getRepository(Task);
|
||||
// 指定した音声ファイルIDに紐づくTaskの中でStatusが[Uploaded,Inprogress,Pending]であるものを取得
|
||||
const task = await taskRepo.findOne({
|
||||
@ -174,6 +189,7 @@ export class TasksRepositoryService {
|
||||
audio_file_id: audio_file_id,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (!task) {
|
||||
throw new TasksNotFoundError(
|
||||
@ -189,6 +205,7 @@ export class TasksRepositoryService {
|
||||
typist_user_id: user_id,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
if (tasks.length > 0) {
|
||||
@ -227,6 +244,7 @@ export class TasksRepositoryService {
|
||||
},
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
// ユーザーの所属するすべてのグループIDを列挙
|
||||
const groupIds = groups.map((member) => member.user_group_id);
|
||||
@ -249,6 +267,7 @@ export class TasksRepositoryService {
|
||||
},
|
||||
],
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
//チェックアウト権限がなければエラー
|
||||
@ -319,6 +338,7 @@ export class TasksRepositoryService {
|
||||
audio_file_id: audio_file_id,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (!task) {
|
||||
throw new TasksNotFoundError(
|
||||
@ -372,6 +392,7 @@ export class TasksRepositoryService {
|
||||
audio_file_id: audio_file_id,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (!task) {
|
||||
throw new TasksNotFoundError(
|
||||
@ -447,6 +468,7 @@ export class TasksRepositoryService {
|
||||
audio_file_id: audio_file_id,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (!task) {
|
||||
throw new TasksNotFoundError(
|
||||
@ -498,6 +520,7 @@ export class TasksRepositoryService {
|
||||
audio_file_id: audio_file_id,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (!task) {
|
||||
throw new TasksNotFoundError(
|
||||
@ -846,6 +869,22 @@ export class TasksRepositoryService {
|
||||
|
||||
const createdEntity = await this.dataSource.transaction(
|
||||
async (entityManager) => {
|
||||
// タスクの所有者の存在確認
|
||||
const userRepo = entityManager.getRepository(User);
|
||||
const user = await userRepo.findOne({
|
||||
where: {
|
||||
id: owner_user_id,
|
||||
account_id: account_id,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (!user) {
|
||||
throw new UserNotFoundError(
|
||||
`User not exists. owner_user_id:${owner_user_id}`,
|
||||
);
|
||||
}
|
||||
|
||||
const audioFileRepo = entityManager.getRepository(AudioFile);
|
||||
const newAudioFile = audioFileRepo.create(audioFile);
|
||||
const savedAudioFile = await insertEntity(
|
||||
@ -865,6 +904,7 @@ export class TasksRepositoryService {
|
||||
where: { account_id: account_id, is_job_number_enabled: true },
|
||||
order: { created_at: 'DESC', job_number: 'DESC' },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
let newJobNumber = '00000001';
|
||||
@ -927,30 +967,6 @@ export class TasksRepositoryService {
|
||||
assignees: Assignee[],
|
||||
): Promise<void> {
|
||||
await this.dataSource.transaction(async (entityManager) => {
|
||||
// UserGroupの取得/存在確認
|
||||
const userGroupIds = assignees
|
||||
.filter((x) => x.typistGroupId !== undefined)
|
||||
.map((y) => {
|
||||
return y.typistGroupId;
|
||||
});
|
||||
const groupRepo = entityManager.getRepository(UserGroup);
|
||||
const groupRecords = await groupRepo.find({
|
||||
where: {
|
||||
id: In(userGroupIds),
|
||||
account_id: account_id,
|
||||
deleted_at: IsNull(),
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
});
|
||||
// idはユニークであるため取得件数の一致でグループの存在を確認
|
||||
if (userGroupIds.length !== groupRecords.length) {
|
||||
throw new TypistUserGroupNotFoundError(
|
||||
`Group not exists Error. reqUserGroupId:${userGroupIds}; resUserGroupId:${groupRecords.map(
|
||||
(x) => x.id,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Userの取得/存在確認
|
||||
const typistUserIds = assignees
|
||||
.filter((x) => x.typistUserId !== undefined)
|
||||
@ -967,6 +983,7 @@ export class TasksRepositoryService {
|
||||
deleted_at: IsNull(),
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
// idはユニークであるため取得件数の一致でユーザーの存在を確認
|
||||
if (typistUserIds.length !== userRecords.length) {
|
||||
@ -977,6 +994,31 @@ export class TasksRepositoryService {
|
||||
);
|
||||
}
|
||||
|
||||
// UserGroupの取得/存在確認
|
||||
const userGroupIds = assignees
|
||||
.filter((x) => x.typistGroupId !== undefined)
|
||||
.map((y) => {
|
||||
return y.typistGroupId;
|
||||
});
|
||||
const groupRepo = entityManager.getRepository(UserGroup);
|
||||
const groupRecords = await groupRepo.find({
|
||||
where: {
|
||||
id: In(userGroupIds),
|
||||
account_id: account_id,
|
||||
deleted_at: IsNull(),
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
// idはユニークであるため取得件数の一致でグループの存在を確認
|
||||
if (userGroupIds.length !== groupRecords.length) {
|
||||
throw new TypistUserGroupNotFoundError(
|
||||
`Group not exists Error. reqUserGroupId:${userGroupIds}; resUserGroupId:${groupRecords.map(
|
||||
(x) => x.id,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 引数audioFileIdを使ってTaskレコードを特定し、そのステータスを取得/存在確認
|
||||
const taskRepo = entityManager.getRepository(Task);
|
||||
|
||||
@ -992,6 +1034,7 @@ export class TasksRepositoryService {
|
||||
},
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
//タスクが存在しない or ステータスがUploadedでなければエラー
|
||||
if (!taskRecord) {
|
||||
@ -1155,39 +1198,51 @@ export class TasksRepositoryService {
|
||||
return await this.dataSource.transaction(async (entityManager) => {
|
||||
// 音声ファイルを取得
|
||||
const audioFileRepo = entityManager.getRepository(AudioFile);
|
||||
const audioFile = await audioFileRepo.findOne({
|
||||
relations: {
|
||||
task: true,
|
||||
},
|
||||
const audio = await audioFileRepo.findOne({
|
||||
where: {
|
||||
id: audioFileId,
|
||||
account_id: accountId,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
});
|
||||
if (!audioFile) {
|
||||
if (!audio) {
|
||||
throw new Error(
|
||||
`audio file not found. audio_file_id:${audioFileId}, accountId:${accountId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const { task } = audioFile;
|
||||
|
||||
if (!task) {
|
||||
throw new Error(
|
||||
`task not found. audio_file_id:${audioFileId}, accountId:${accountId}`,
|
||||
);
|
||||
}
|
||||
// authorIdをもとにユーザーを取得
|
||||
const userRepo = entityManager.getRepository(User);
|
||||
const authorUser = await userRepo.findOne({
|
||||
where: {
|
||||
author_id: audioFile.author_id,
|
||||
author_id: audio.author_id,
|
||||
account_id: accountId,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
// TaskとFileを取得
|
||||
const taskRepo = entityManager.getRepository(Task);
|
||||
const task = await taskRepo.findOne({
|
||||
relations: {
|
||||
file: true,
|
||||
},
|
||||
where: {
|
||||
account_id: accountId,
|
||||
audio_file_id: audioFileId,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
const audioFile = task?.file;
|
||||
if (!audioFile) {
|
||||
throw new Error(
|
||||
`audio file not found. audio_file_id:${audioFileId}, accountId:${accountId}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 音声ファイル上のworktypeIdをもとにworktypeを取得
|
||||
const worktypeRepo = entityManager.getRepository(Worktype);
|
||||
const worktypeRecord = await worktypeRepo.findOne({
|
||||
@ -1196,6 +1251,7 @@ export class TasksRepositoryService {
|
||||
account_id: accountId,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
// 音声ファイル上のworktypeIdが設定されているが、一致するworktypeが存在しない場合はエラーを出して終了
|
||||
@ -1217,6 +1273,7 @@ export class TasksRepositoryService {
|
||||
worktype_id: worktypeRecord?.id ?? IsNull(),
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
// Workflow(ルーティングルール)があればタスクのチェックアウト権限を設定する
|
||||
@ -1244,6 +1301,7 @@ export class TasksRepositoryService {
|
||||
account_id: accountId,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (!myAuthorUser) {
|
||||
throw new Error(
|
||||
@ -1260,6 +1318,7 @@ export class TasksRepositoryService {
|
||||
worktype_id: worktypeRecord?.id ?? IsNull(),
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
// API実行者のAuthorIdと音声ファイルのWorktypeをもとにルーティングルールを取得できない場合はエラーを出して終了
|
||||
@ -1326,6 +1385,7 @@ export class TasksRepositoryService {
|
||||
const typistUsers = await userRepo.find({
|
||||
where: { account_id: accountId, id: In(typistIds) },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (typistUsers.length !== typistIds.length) {
|
||||
throw new Error(`typist not found. ids: ${typistIds}`);
|
||||
@ -1339,6 +1399,7 @@ export class TasksRepositoryService {
|
||||
const typistGroups = await userGroupRepo.find({
|
||||
where: { account_id: accountId, id: In(groupIds) },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (typistGroups.length !== groupIds.length) {
|
||||
throw new Error(`typist group not found. ids: ${groupIds}`);
|
||||
|
||||
@ -52,6 +52,7 @@ export class TemplateFilesRepositoryService {
|
||||
const template = await templateFilesRepo.findOne({
|
||||
where: { account_id: accountId, file_name: fileName },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
// 同名ファイルは同じものとして扱うため、すでにファイルがあれば更新(更新日時の履歴を残しておきたい)
|
||||
|
||||
@ -12,3 +12,10 @@ export class TypistIdInvalidError extends Error {
|
||||
this.name = 'TypistIdInvalidError';
|
||||
}
|
||||
}
|
||||
// 同名のタイピストグループが存在する場合のエラー
|
||||
export class TypistGroupNameAlreadyExistError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'TypistGroupNameAlreadyExistError';
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,13 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DataSource, In, IsNull } from 'typeorm';
|
||||
import { DataSource, In, IsNull, Not } from 'typeorm';
|
||||
import { UserGroup } from './entity/user_group.entity';
|
||||
import { UserGroupMember } from './entity/user_group_member.entity';
|
||||
import { User } from '../users/entity/user.entity';
|
||||
import { TypistGroupNotExistError, TypistIdInvalidError } from './errors/types';
|
||||
import {
|
||||
TypistGroupNameAlreadyExistError,
|
||||
TypistGroupNotExistError,
|
||||
TypistIdInvalidError,
|
||||
} from './errors/types';
|
||||
import { USER_ROLES } from '../../constants';
|
||||
import {
|
||||
insertEntities,
|
||||
@ -122,6 +126,7 @@ export class UserGroupsRepositoryService {
|
||||
role: USER_ROLES.TYPIST,
|
||||
email_verified: true,
|
||||
},
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
});
|
||||
if (userRecords.length !== typistIds.length) {
|
||||
@ -131,6 +136,19 @@ export class UserGroupsRepositoryService {
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
// 同名のタイピストグループが存在するか確認する
|
||||
const sameNameTypistGroup = await userGroupRepo.findOne({
|
||||
where: {
|
||||
name,
|
||||
account_id: accountId,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
});
|
||||
if (sameNameTypistGroup) {
|
||||
throw new TypistGroupNameAlreadyExistError(
|
||||
`TypistGroup already exists Error. accountId: ${accountId}; name: ${name}`,
|
||||
);
|
||||
}
|
||||
// userGroupをDBに保存する
|
||||
const userGroup = await insertEntity(
|
||||
UserGroup,
|
||||
@ -188,6 +206,7 @@ export class UserGroupsRepositoryService {
|
||||
role: USER_ROLES.TYPIST,
|
||||
email_verified: true,
|
||||
},
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
});
|
||||
if (userRecords.length !== typistIds.length) {
|
||||
@ -198,12 +217,28 @@ export class UserGroupsRepositoryService {
|
||||
);
|
||||
}
|
||||
|
||||
// 同名のタイピストグループが存在するか確認する
|
||||
const sameNameTypistGroup = await userGroupRepo.findOne({
|
||||
where: {
|
||||
id: Not(typistGroupId),
|
||||
name: typistGroupName,
|
||||
account_id: accountId,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
});
|
||||
if (sameNameTypistGroup) {
|
||||
throw new TypistGroupNameAlreadyExistError(
|
||||
`TypistGroup already exists Error. accountId: ${accountId}; name: ${typistGroupName}`,
|
||||
);
|
||||
}
|
||||
|
||||
// GroupIdが自アカウント内に存在するか確認する
|
||||
const typistGroup = await userGroupRepo.findOne({
|
||||
where: {
|
||||
id: typistGroupId,
|
||||
account_id: accountId,
|
||||
},
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
});
|
||||
if (!typistGroup) {
|
||||
|
||||
@ -289,6 +289,7 @@ export class UsersRepositoryService {
|
||||
const targetUser = await repo.findOne({
|
||||
where: { id: id, account_id: accountId },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
// 運用上ユーザがいないことはあり得ないが、プログラム上発生しうるのでエラーとして処理
|
||||
|
||||
@ -87,6 +87,7 @@ export class WorkflowsRepositoryService {
|
||||
const author = await userRepo.findOne({
|
||||
where: { account_id: accountId, id: authorId, email_verified: true },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (!author) {
|
||||
throw new UserNotFoundError(
|
||||
@ -100,6 +101,7 @@ export class WorkflowsRepositoryService {
|
||||
const worktypes = await worktypeRepo.find({
|
||||
where: { account_id: accountId, id: worktypeId },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (worktypes.length === 0) {
|
||||
throw new WorktypeIdNotFoundError(
|
||||
@ -114,6 +116,7 @@ export class WorkflowsRepositoryService {
|
||||
const template = await templateRepo.findOne({
|
||||
where: { account_id: accountId, id: templateId },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (!template) {
|
||||
throw new TemplateFileNotExistError('template not found.');
|
||||
@ -131,6 +134,7 @@ export class WorkflowsRepositoryService {
|
||||
email_verified: true,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (typistUsers.length !== typistIds.length) {
|
||||
throw new UserNotFoundError(
|
||||
@ -146,6 +150,7 @@ export class WorkflowsRepositoryService {
|
||||
const typistGroups = await userGroupRepo.find({
|
||||
where: { account_id: accountId, id: In(groupIds) },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (typistGroups.length !== groupIds.length) {
|
||||
throw new TypistGroupNotExistError(
|
||||
@ -163,6 +168,7 @@ export class WorkflowsRepositoryService {
|
||||
worktype_id: worktypeId !== undefined ? worktypeId : IsNull(),
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (workflow.length !== 0) {
|
||||
throw new AuthorIdAndWorktypeIdPairAlreadyExistsError(
|
||||
@ -227,23 +233,12 @@ export class WorkflowsRepositoryService {
|
||||
): Promise<void> {
|
||||
return await this.dataSource.transaction(async (entityManager) => {
|
||||
const workflowRepo = entityManager.getRepository(Workflow);
|
||||
|
||||
// ワークフローの存在確認
|
||||
const targetWorkflow = await workflowRepo.findOne({
|
||||
where: { account_id: accountId, id: workflowId },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
});
|
||||
if (!targetWorkflow) {
|
||||
throw new WorkflowNotFoundError(
|
||||
`workflow not found. id: ${workflowId}`,
|
||||
);
|
||||
}
|
||||
|
||||
// authorの存在確認
|
||||
const userRepo = entityManager.getRepository(User);
|
||||
const author = await userRepo.findOne({
|
||||
where: { account_id: accountId, id: authorId, email_verified: true },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (!author) {
|
||||
throw new UserNotFoundError(
|
||||
@ -251,12 +246,44 @@ export class WorkflowsRepositoryService {
|
||||
);
|
||||
}
|
||||
|
||||
// ルーティング候補ユーザーの存在確認
|
||||
const typistIds = typists.flatMap((typist) =>
|
||||
typist.typistId ? [typist.typistId] : [],
|
||||
);
|
||||
const typistUsers = await userRepo.find({
|
||||
where: {
|
||||
account_id: accountId,
|
||||
id: In(typistIds),
|
||||
email_verified: true,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (typistUsers.length !== typistIds.length) {
|
||||
throw new UserNotFoundError(
|
||||
`typist not found or email not verified. ids: ${typistIds}`,
|
||||
);
|
||||
}
|
||||
|
||||
// ワークフローの存在確認
|
||||
const targetWorkflow = await workflowRepo.findOne({
|
||||
where: { account_id: accountId, id: workflowId },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (!targetWorkflow) {
|
||||
throw new WorkflowNotFoundError(
|
||||
`workflow not found. id: ${workflowId}`,
|
||||
);
|
||||
}
|
||||
|
||||
// worktypeの存在確認
|
||||
if (worktypeId !== undefined) {
|
||||
const worktypeRepo = entityManager.getRepository(Worktype);
|
||||
const worktypes = await worktypeRepo.find({
|
||||
where: { account_id: accountId, id: worktypeId },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (worktypes.length === 0) {
|
||||
throw new WorktypeIdNotFoundError(
|
||||
@ -271,6 +298,7 @@ export class WorkflowsRepositoryService {
|
||||
const template = await templateRepo.findOne({
|
||||
where: { account_id: accountId, id: templateId },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (!template) {
|
||||
throw new TemplateFileNotExistError(
|
||||
@ -279,24 +307,6 @@ export class WorkflowsRepositoryService {
|
||||
}
|
||||
}
|
||||
|
||||
// ルーティング候補ユーザーの存在確認
|
||||
const typistIds = typists.flatMap((typist) =>
|
||||
typist.typistId ? [typist.typistId] : [],
|
||||
);
|
||||
const typistUsers = await userRepo.find({
|
||||
where: {
|
||||
account_id: accountId,
|
||||
id: In(typistIds),
|
||||
email_verified: true,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
});
|
||||
if (typistUsers.length !== typistIds.length) {
|
||||
throw new UserNotFoundError(
|
||||
`typist not found or email not verified. ids: ${typistIds}`,
|
||||
);
|
||||
}
|
||||
|
||||
// ルーティング候補ユーザーグループの存在確認
|
||||
const groupIds = typists.flatMap((typist) => {
|
||||
return typist.typistGroupId ? [typist.typistGroupId] : [];
|
||||
@ -305,6 +315,7 @@ export class WorkflowsRepositoryService {
|
||||
const typistGroups = await userGroupRepo.find({
|
||||
where: { account_id: accountId, id: In(groupIds) },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (typistGroups.length !== groupIds.length) {
|
||||
throw new TypistGroupNotExistError(
|
||||
@ -399,6 +410,7 @@ export class WorkflowsRepositoryService {
|
||||
const workflow = await workflowRepo.findOne({
|
||||
where: { account_id: accountId, id: workflowId },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (!workflow) {
|
||||
throw new WorkflowNotFoundError(
|
||||
|
||||
@ -88,6 +88,7 @@ export class WorktypesRepositoryService {
|
||||
const duplicatedWorktype = await worktypeRepo.findOne({
|
||||
where: { account_id: accountId, custom_worktype_id: worktypeId },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
// ワークタイプIDが重複している場合はエラー
|
||||
@ -100,6 +101,7 @@ export class WorktypesRepositoryService {
|
||||
const worktypeCount = await worktypeRepo.count({
|
||||
where: { account_id: accountId },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
// ワークタイプの登録数が上限に達している場合はエラー
|
||||
@ -163,6 +165,7 @@ export class WorktypesRepositoryService {
|
||||
const worktype = await worktypeRepo.findOne({
|
||||
where: { account_id: accountId, id: id },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
// ワークタイプが存在しない場合はエラー
|
||||
@ -177,6 +180,7 @@ export class WorktypesRepositoryService {
|
||||
id: Not(id),
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
// ワークタイプIDが重複している場合はエラー
|
||||
@ -216,6 +220,7 @@ export class WorktypesRepositoryService {
|
||||
const worktype = await worktypeRepo.findOne({
|
||||
where: { account_id: accountId, id: id },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
// ワークタイプが存在しない場合はエラー
|
||||
if (!worktype) {
|
||||
@ -227,6 +232,7 @@ export class WorktypesRepositoryService {
|
||||
const account = await accountRepo.findOne({
|
||||
where: { id: accountId },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
if (account?.active_worktype_id === id) {
|
||||
@ -244,6 +250,7 @@ export class WorktypesRepositoryService {
|
||||
const workflows = await workflowRepo.find({
|
||||
where: { account_id: accountId, worktype_id: id },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (workflows.length > 0) {
|
||||
const workflowIds = workflows.map((workflow) => workflow.id);
|
||||
@ -322,6 +329,7 @@ export class WorktypesRepositoryService {
|
||||
const worktype = await worktypeRepo.findOne({
|
||||
where: { account_id: accountId, id: worktypeId },
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
// ワークタイプが存在しない場合はエラー
|
||||
if (!worktype) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user