Merge branch 'develop' into main
This commit is contained in:
commit
19f1997bd6
@ -2,6 +2,6 @@
|
||||
"$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
|
||||
"spaces": 2,
|
||||
"generator-cli": {
|
||||
"version": "7.0.0"
|
||||
"version": "7.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ const App = (): JSX.Element => {
|
||||
(e: AxiosError) => {
|
||||
if (e?.response?.status === 401) {
|
||||
dispatch(clearToken());
|
||||
instance.logout({
|
||||
instance.logoutRedirect({
|
||||
postLogoutRedirectUri: "/?logout=true",
|
||||
});
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ import PartnerPage from "pages/PartnerPage";
|
||||
import WorkflowPage from "pages/WorkflowPage";
|
||||
import TypistGroupSettingPage from "pages/TypistGroupSettingPage";
|
||||
import WorktypeIdSettingPage from "pages/WorkTypeIdSettingPage";
|
||||
import AccountPage from "pages/AccountPage";
|
||||
|
||||
const AppRouter: React.FC = () => (
|
||||
<Routes>
|
||||
@ -53,7 +54,7 @@ const AppRouter: React.FC = () => (
|
||||
{/* XXX ヘッダーの挙動確認のため仮のページを作成 */}
|
||||
<Route
|
||||
path="/account"
|
||||
element={<RouteAuthGuard component={<SamplePage />} />}
|
||||
element={<RouteAuthGuard component={<AccountPage />} />}
|
||||
/>
|
||||
<Route
|
||||
path="/dictations"
|
||||
|
||||
@ -1 +1 @@
|
||||
7.0.0
|
||||
7.0.1
|
||||
@ -54,6 +54,48 @@ export interface Account {
|
||||
* @memberof Account
|
||||
*/
|
||||
'companyName': string;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof Account
|
||||
*/
|
||||
'tier': number;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Account
|
||||
*/
|
||||
'country': string;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof Account
|
||||
*/
|
||||
'parentAccountId'?: number;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof Account
|
||||
*/
|
||||
'delegationPermission': boolean;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof Account
|
||||
*/
|
||||
'primaryAdminUserId'?: number;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof Account
|
||||
*/
|
||||
'secondryAdminUserId'?: number;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Account
|
||||
*/
|
||||
'parentAccountName'?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@ -824,7 +866,7 @@ export interface GetRelationsResponse {
|
||||
* @type {string}
|
||||
* @memberof GetRelationsResponse
|
||||
*/
|
||||
'encryptionPassword': string | null;
|
||||
'encryptionPassword'?: string;
|
||||
/**
|
||||
* アカウントがデフォルトで利用するWorkTypeID(アカウントに紐づくWorkTypeIDから一つ指定。activeWorktypeがなければ空文字を返却する)
|
||||
* @type {string}
|
||||
@ -969,7 +1011,7 @@ export interface GetWorktypesResponse {
|
||||
* @type {number}
|
||||
* @memberof GetWorktypesResponse
|
||||
*/
|
||||
'acrive'?: number;
|
||||
'active'?: number;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@ -1307,12 +1349,6 @@ export interface PostUpdateUserRequest {
|
||||
* @interface PostWorktypeOptionItem
|
||||
*/
|
||||
export interface PostWorktypeOptionItem {
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof PostWorktypeOptionItem
|
||||
*/
|
||||
'id': number;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
@ -1682,7 +1718,7 @@ export interface UpdateAccountInfoRequest {
|
||||
* @type {number}
|
||||
* @memberof UpdateAccountInfoRequest
|
||||
*/
|
||||
'parentAccountId': number;
|
||||
'parentAccountId'?: number;
|
||||
/**
|
||||
* 代行操作許可
|
||||
* @type {boolean}
|
||||
@ -1700,7 +1736,7 @@ export interface UpdateAccountInfoRequest {
|
||||
* @type {number}
|
||||
* @memberof UpdateAccountInfoRequest
|
||||
*/
|
||||
'secondryAdminUserId': number;
|
||||
'secondryAdminUserId'?: number;
|
||||
}
|
||||
/**
|
||||
*
|
||||
|
||||
@ -15,6 +15,7 @@ import partner from "features/partner/partnerSlice";
|
||||
import licenseOrderHistory from "features/license/licenseOrderHistory/licenseOrderHistorySlice";
|
||||
import typistGroup from "features/workflow/typistGroup/typistGroupSlice";
|
||||
import worktype from "features/workflow/worktype/worktypeSlice";
|
||||
import account from "features/account/accountSlice";
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
@ -34,6 +35,7 @@ export const store = configureStore({
|
||||
partner,
|
||||
typistGroup,
|
||||
worktype,
|
||||
account,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -36,6 +36,7 @@ export const errorCodes = [
|
||||
"E010302", // authorId重複エラー
|
||||
"E010401", // PONumber重複エラー
|
||||
"E010501", // アカウント不在エラー
|
||||
"E010502", // アカウント情報変更不可エラー
|
||||
"E010601", // タスク変更不可エラー(タスクが変更できる状態でない、またはタスクが存在しない)
|
||||
"E010602", // タスク変更権限不足エラー
|
||||
"E010603", // タスク不在エラー
|
||||
|
||||
@ -23,7 +23,7 @@ export const RouteAuthGuard = (props: RouteAuthGuardProps) => {
|
||||
if (!isAuth || isExpired) {
|
||||
dispatch(clearToken());
|
||||
// B2Cからもログアウトする
|
||||
instance.logout({
|
||||
instance.logoutRedirect({
|
||||
postLogoutRedirectUri: "/?logout=true",
|
||||
});
|
||||
}
|
||||
|
||||
107
dictation_client/src/features/account/accountSlice.ts
Normal file
107
dictation_client/src/features/account/accountSlice.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { AccountState } from "./state";
|
||||
import { updateAccountInfoAsync, getAccountRelationsAsync } from "./operations";
|
||||
|
||||
const initialState: AccountState = {
|
||||
domain: {
|
||||
getAccountInfo: {
|
||||
account: {
|
||||
accountId: 0,
|
||||
companyName: "",
|
||||
tier: 0,
|
||||
country: "",
|
||||
delegationPermission: false,
|
||||
},
|
||||
},
|
||||
dealers: [],
|
||||
users: [],
|
||||
},
|
||||
|
||||
apps: {
|
||||
updateAccountInfo: {
|
||||
parentAccountId: undefined,
|
||||
delegationPermission: false,
|
||||
primaryAdminUserId: 0,
|
||||
secondryAdminUserId: undefined,
|
||||
},
|
||||
isLoading: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const accountSlice = createSlice({
|
||||
name: "account",
|
||||
initialState,
|
||||
reducers: {
|
||||
changeDealer: (
|
||||
state,
|
||||
action: PayloadAction<{ parentAccountId: number | undefined }>
|
||||
) => {
|
||||
const { parentAccountId } = action.payload;
|
||||
state.apps.updateAccountInfo.parentAccountId = parentAccountId;
|
||||
},
|
||||
changeDealerPermission: (
|
||||
state,
|
||||
action: PayloadAction<{ delegationPermission: boolean }>
|
||||
) => {
|
||||
const { delegationPermission } = action.payload;
|
||||
state.apps.updateAccountInfo.delegationPermission = delegationPermission;
|
||||
},
|
||||
changePrimaryAdministrator: (
|
||||
state,
|
||||
action: PayloadAction<{ primaryAdminUserId: number }>
|
||||
) => {
|
||||
const { primaryAdminUserId } = action.payload;
|
||||
state.apps.updateAccountInfo.primaryAdminUserId = primaryAdminUserId;
|
||||
},
|
||||
changeSecondryAdministrator: (
|
||||
state,
|
||||
action: PayloadAction<{ secondryAdminUserId: number | undefined }>
|
||||
) => {
|
||||
const { secondryAdminUserId } = action.payload;
|
||||
state.apps.updateAccountInfo.secondryAdminUserId = secondryAdminUserId;
|
||||
},
|
||||
cleanupApps: (state) => {
|
||||
state.domain = initialState.domain;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(getAccountRelationsAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(getAccountRelationsAsync.fulfilled, (state, action) => {
|
||||
state.domain.getAccountInfo = action.payload.accountInfo;
|
||||
state.domain.dealers = action.payload.dealers.dealers;
|
||||
state.domain.users = action.payload.users.users;
|
||||
state.apps.updateAccountInfo.parentAccountId =
|
||||
action.payload.accountInfo.account.parentAccountId;
|
||||
state.apps.updateAccountInfo.delegationPermission =
|
||||
action.payload.accountInfo.account.delegationPermission;
|
||||
if (action.payload.accountInfo.account.primaryAdminUserId)
|
||||
state.apps.updateAccountInfo.primaryAdminUserId =
|
||||
action.payload.accountInfo.account.primaryAdminUserId;
|
||||
state.apps.updateAccountInfo.secondryAdminUserId =
|
||||
action.payload.accountInfo.account.secondryAdminUserId;
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(getAccountRelationsAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(updateAccountInfoAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(updateAccountInfoAsync.fulfilled, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(updateAccountInfoAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
export const {
|
||||
changeDealer,
|
||||
changeDealerPermission,
|
||||
changePrimaryAdministrator,
|
||||
changeSecondryAdministrator,
|
||||
cleanupApps,
|
||||
} = accountSlice.actions;
|
||||
export default accountSlice.reducer;
|
||||
5
dictation_client/src/features/account/index.ts
Normal file
5
dictation_client/src/features/account/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from "./state";
|
||||
export * from "./operations";
|
||||
export * from "./accountSlice";
|
||||
export * from "./selectors";
|
||||
export * from "./types";
|
||||
105
dictation_client/src/features/account/operations.ts
Normal file
105
dictation_client/src/features/account/operations.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import type { RootState } from "app/store";
|
||||
import { ErrorObject, createErrorObject } from "common/errors";
|
||||
import { getTranslationID } from "translation";
|
||||
import { openSnackbar } from "features/ui/uiSlice";
|
||||
import { AccountsApi, UpdateAccountInfoRequest, UsersApi } from "../../api/api";
|
||||
import { Configuration } from "../../api/configuration";
|
||||
import { ViewAccountRelationsInfo } from "./types";
|
||||
|
||||
export const getAccountRelationsAsync = createAsyncThunk<
|
||||
// 正常時の戻り値の型
|
||||
ViewAccountRelationsInfo,
|
||||
void,
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("accounts/getAccountRelationsAsync", async (_args, thunkApi) => {
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration, accessToken } = state.auth;
|
||||
const config = new Configuration(configuration);
|
||||
const accountsApi = new AccountsApi(config);
|
||||
const usersApi = new UsersApi(config);
|
||||
|
||||
try {
|
||||
const accountInfo = await accountsApi.getMyAccount({
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
const dealers = await accountsApi.getDealers();
|
||||
const users = await usersApi.getUsers({
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
return {
|
||||
accountInfo: accountInfo.data,
|
||||
dealers: dealers.data,
|
||||
users: users.data,
|
||||
};
|
||||
} catch (e) {
|
||||
// e ⇒ errorObjectに変換"
|
||||
const error = createErrorObject(e);
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: getTranslationID("common.message.internalServerError"),
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
export const updateAccountInfoAsync = createAsyncThunk<
|
||||
{
|
||||
/* Empty Object */
|
||||
},
|
||||
UpdateAccountInfoRequest,
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("accounts/updateAccountInfoAsync", async (args, thunkApi) => {
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration, accessToken } = state.auth;
|
||||
const config = new Configuration(configuration);
|
||||
const accountApi = new AccountsApi(config);
|
||||
|
||||
try {
|
||||
await accountApi.me(args, {
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "info",
|
||||
message: getTranslationID("common.message.success"),
|
||||
})
|
||||
);
|
||||
return {};
|
||||
} catch (e) {
|
||||
const error = createErrorObject(e);
|
||||
|
||||
let errorMessage = getTranslationID("common.message.internalServerError");
|
||||
|
||||
if (error.code === "E010502") {
|
||||
errorMessage = getTranslationID(
|
||||
"accountPage.message.updateAccountFailedError"
|
||||
);
|
||||
}
|
||||
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: errorMessage,
|
||||
})
|
||||
);
|
||||
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
17
dictation_client/src/features/account/selectors.ts
Normal file
17
dictation_client/src/features/account/selectors.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Dealer } from "api/api";
|
||||
import { RootState } from "app/store";
|
||||
|
||||
export const selectAccountInfo = (state: RootState) =>
|
||||
state.account.domain.getAccountInfo;
|
||||
export const selectAllDealers = (state: RootState) =>
|
||||
state.account.domain.dealers;
|
||||
export const selectDealers = (state: RootState) => {
|
||||
const { dealers } = state.account.domain;
|
||||
const { country } = state.account.domain.getAccountInfo.account;
|
||||
return dealers.filter((x: Dealer) => x.country === country);
|
||||
};
|
||||
export const selectUsers = (state: RootState) => state.account.domain.users;
|
||||
export const selectIsLoading = (state: RootState) =>
|
||||
state.account.apps.isLoading;
|
||||
export const selectUpdateAccountInfo = (state: RootState) =>
|
||||
state.account.apps.updateAccountInfo;
|
||||
22
dictation_client/src/features/account/state.ts
Normal file
22
dictation_client/src/features/account/state.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import {
|
||||
UpdateAccountInfoRequest,
|
||||
GetMyAccountResponse,
|
||||
Dealer,
|
||||
User,
|
||||
} from "../../api/api";
|
||||
|
||||
export interface AccountState {
|
||||
domain: Domain;
|
||||
apps: Apps;
|
||||
}
|
||||
|
||||
export interface Domain {
|
||||
getAccountInfo: GetMyAccountResponse;
|
||||
dealers: Dealer[];
|
||||
users: User[];
|
||||
}
|
||||
|
||||
export interface Apps {
|
||||
updateAccountInfo: UpdateAccountInfoRequest;
|
||||
isLoading: boolean;
|
||||
}
|
||||
11
dictation_client/src/features/account/types.ts
Normal file
11
dictation_client/src/features/account/types.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import {
|
||||
GetMyAccountResponse,
|
||||
GetDealersResponse,
|
||||
GetUsersResponse,
|
||||
} from "../../api/api";
|
||||
|
||||
export interface ViewAccountRelationsInfo {
|
||||
accountInfo: GetMyAccountResponse;
|
||||
dealers: GetDealersResponse;
|
||||
users: GetUsersResponse;
|
||||
}
|
||||
@ -6,7 +6,13 @@ import { ACCOUNTS_VIEW_LIMIT } from "./constants";
|
||||
|
||||
const initialState: PartnerLicensesState = {
|
||||
domain: {
|
||||
myAccountInfo: { accountId: 0, companyName: "" },
|
||||
myAccountInfo: {
|
||||
accountId: 0,
|
||||
companyName: "",
|
||||
tier: 0,
|
||||
country: "",
|
||||
delegationPermission: false,
|
||||
},
|
||||
total: 0,
|
||||
ownPartnerLicense: {
|
||||
accountId: 0,
|
||||
|
||||
@ -284,3 +284,61 @@ export const editOptionItemsAsync = createAsyncThunk<
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
export const updateActiveWorktypeAsync = createAsyncThunk<
|
||||
{
|
||||
// return empty
|
||||
},
|
||||
{ id?: number | undefined },
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("workflow/updateActiveWorktypeAsync", async (args, thunkApi) => {
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration, accessToken } = state.auth;
|
||||
const config = new Configuration(configuration);
|
||||
const accountsApi = new AccountsApi(config);
|
||||
const { id } = args;
|
||||
|
||||
try {
|
||||
await accountsApi.activeWorktype(
|
||||
{ id },
|
||||
{
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
}
|
||||
);
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "info",
|
||||
message: getTranslationID("common.message.success"),
|
||||
})
|
||||
);
|
||||
|
||||
return {};
|
||||
} catch (e) {
|
||||
// e ⇒ errorObjectに変換"
|
||||
const error = createErrorObject(e);
|
||||
|
||||
let errorMessage = getTranslationID("common.message.internalServerError");
|
||||
|
||||
// ActiveWorktypeの保存に失敗した場合
|
||||
if (error.code === "E011003") {
|
||||
errorMessage = getTranslationID(
|
||||
"worktypeIdSetting.message.updateActiveWorktypeFailedError"
|
||||
);
|
||||
}
|
||||
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: errorMessage,
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
@ -70,3 +70,6 @@ export const selectHasErrorOptionItems = (state: RootState) => {
|
||||
// isOptionItemsLoadingを取得する
|
||||
export const selectIsOptionItemsLoading = (state: RootState) =>
|
||||
state.worktype.apps.isOptionItemsLoading;
|
||||
|
||||
export const selectActiveWorktypeId = (state: RootState) =>
|
||||
state.worktype.apps.activeWorktypeId;
|
||||
|
||||
@ -15,6 +15,7 @@ export interface Apps {
|
||||
worktypeId: string;
|
||||
description?: string;
|
||||
optionItems?: OptionItem[];
|
||||
activeWorktypeId?: number | undefined;
|
||||
}
|
||||
|
||||
export interface Domain {
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
editWorktypeAsync,
|
||||
getOptionItemsAsync,
|
||||
listWorktypesAsync,
|
||||
updateActiveWorktypeAsync,
|
||||
} from "./operations";
|
||||
import { OptionItem, isOptionItemDefaultValueType } from "./types";
|
||||
import { OPTION_ITEMS_DEFAULT_VALUE_TYPE } from "./constants";
|
||||
@ -20,6 +21,7 @@ const initialState: WorktypeState = {
|
||||
worktypeId: "",
|
||||
description: undefined,
|
||||
optionItems: undefined,
|
||||
activeWorktypeId: undefined,
|
||||
},
|
||||
domain: {},
|
||||
};
|
||||
@ -82,9 +84,10 @@ export const worktypeSlice = createSlice({
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(listWorktypesAsync.fulfilled, (state, action) => {
|
||||
// TODO:Active WorktypeIDも取得する
|
||||
const { worktypes } = action.payload;
|
||||
const { worktypes, active } = action.payload;
|
||||
|
||||
state.domain.worktypes = worktypes;
|
||||
state.apps.activeWorktypeId = active;
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(listWorktypesAsync.rejected, (state) => {
|
||||
@ -137,6 +140,15 @@ export const worktypeSlice = createSlice({
|
||||
builder.addCase(editOptionItemsAsync.rejected, (state) => {
|
||||
state.apps.isOptionItemsLoading = false;
|
||||
});
|
||||
builder.addCase(updateActiveWorktypeAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(updateActiveWorktypeAsync.fulfilled, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(updateActiveWorktypeAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
export const {
|
||||
|
||||
350
dictation_client/src/pages/AccountPage/index.tsx
Normal file
350
dictation_client/src/pages/AccountPage/index.tsx
Normal file
@ -0,0 +1,350 @@
|
||||
import { AppDispatch } from "app/store";
|
||||
import { UpdateTokenTimer } from "components/auth/updateTokenTimer";
|
||||
import Footer from "components/footer";
|
||||
import Header from "components/header";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import styles from "styles/app.module.scss";
|
||||
import {
|
||||
changeDealer,
|
||||
changeDealerPermission,
|
||||
changePrimaryAdministrator,
|
||||
changeSecondryAdministrator,
|
||||
getAccountRelationsAsync,
|
||||
selectAccountInfo,
|
||||
selectDealers,
|
||||
selectIsLoading,
|
||||
selectUpdateAccountInfo,
|
||||
selectUsers,
|
||||
updateAccountInfoAsync,
|
||||
} from "features/account/index";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getTranslationID } from "translation";
|
||||
import { TIERS } from "components/auth/constants";
|
||||
import { isApproveTier } from "features/auth/utils";
|
||||
import progress_activit from "../../assets/images/progress_activit.svg";
|
||||
|
||||
const AccountPage: React.FC = (): JSX.Element => {
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
const [t] = useTranslation();
|
||||
const viewInfo = useSelector(selectAccountInfo);
|
||||
const dealers = useSelector(selectDealers);
|
||||
const users = useSelector(selectUsers);
|
||||
const isLoading = useSelector(selectIsLoading);
|
||||
const updateAccountInfo = useSelector(selectUpdateAccountInfo);
|
||||
|
||||
// ユーザーが第5階層であるかどうかを判定する
|
||||
const isTier5 = isApproveTier([TIERS.TIER5]);
|
||||
|
||||
// 階層表示用
|
||||
const tierNames: { [key: number]: string } = {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
1: t(getTranslationID("common.label.tier1")),
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
2: t(getTranslationID("common.label.tier2")),
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
3: t(getTranslationID("common.label.tier3")),
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
4: t(getTranslationID("common.label.tier4")),
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
5: t(getTranslationID("common.label.tier5")),
|
||||
};
|
||||
|
||||
// 画面起動時
|
||||
useEffect(() => {
|
||||
dispatch(getAccountRelationsAsync());
|
||||
}, [dispatch]);
|
||||
|
||||
const [isPushSaveChangesButton, setIsPushSaveChangesButton] =
|
||||
useState<boolean>(false);
|
||||
const [isEmptyPrimaryAdmin, setIsEmptyPrimaryAdmin] =
|
||||
useState<boolean>(false);
|
||||
// SaveChanges押下時
|
||||
const onSaveChangesButton = useCallback(async () => {
|
||||
setIsPushSaveChangesButton(true);
|
||||
if (!updateAccountInfo.primaryAdminUserId) {
|
||||
setIsEmptyPrimaryAdmin(true);
|
||||
return;
|
||||
}
|
||||
await dispatch(updateAccountInfoAsync(updateAccountInfo));
|
||||
dispatch(getAccountRelationsAsync());
|
||||
setIsEmptyPrimaryAdmin(false);
|
||||
setIsPushSaveChangesButton(false);
|
||||
}, [dispatch, updateAccountInfo]);
|
||||
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<Header userName="XXXXXX" />
|
||||
<UpdateTokenTimer />
|
||||
<main className={styles.main}>
|
||||
<div>
|
||||
<div className={styles.pageHeader}>
|
||||
<h1 className={styles.pageTitle}>
|
||||
{t(getTranslationID("accountPage.label.title"))}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<section className={styles.account}>
|
||||
<div className={styles.boxFlex}>
|
||||
<ul className={`${styles.menuAction} ${styles.box100}`}>
|
||||
<li>
|
||||
<a
|
||||
href="account_setting.html"
|
||||
className={`${styles.menuLink} ${styles.isActive}`}
|
||||
>
|
||||
<img
|
||||
src="images/file_delete.svg"
|
||||
alt=""
|
||||
className={styles.menuIcon}
|
||||
/>
|
||||
{t(getTranslationID("accountPage.label.fileDeleteSetting"))}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div className={styles.marginRgt3}>
|
||||
<dl className={styles.listVertical}>
|
||||
<h4 className={styles.listHeader}>
|
||||
{t(
|
||||
getTranslationID("accountPage.label.accountInformation")
|
||||
)}
|
||||
</h4>
|
||||
<dt>
|
||||
{t(getTranslationID("accountPage.label.companyName"))}
|
||||
</dt>
|
||||
<dd>{viewInfo.account.companyName}</dd>
|
||||
<dt>{t(getTranslationID("accountPage.label.accountID"))}</dt>
|
||||
<dd>{viewInfo.account.accountId}</dd>
|
||||
<dt>
|
||||
{t(getTranslationID("accountPage.label.yourCategory"))}
|
||||
</dt>
|
||||
<dd>{tierNames[viewInfo.account.tier]}</dd>
|
||||
<dt>
|
||||
{t(getTranslationID("accountPage.label.yourCountry"))}
|
||||
</dt>
|
||||
<dd>{viewInfo.account.country}</dd>
|
||||
<dt>{t(getTranslationID("accountPage.label.yourDealer"))}</dt>
|
||||
{isTier5 && !viewInfo.account.parentAccountName && (
|
||||
<dd className={styles.form}>
|
||||
<select
|
||||
className={`${styles.formInput} ${styles.required}`}
|
||||
onChange={(event) => {
|
||||
dispatch(
|
||||
changeDealer({
|
||||
parentAccountId:
|
||||
dealers.find(
|
||||
(x) => x.name === event.target.value
|
||||
)?.id || undefined,
|
||||
})
|
||||
);
|
||||
}}
|
||||
>
|
||||
<option value="">
|
||||
{t(
|
||||
getTranslationID("accountPage.label.selectDealer")
|
||||
)}
|
||||
</option>
|
||||
{/* eslint-disable-next-line jsx-a11y/control-has-associated-label */}
|
||||
<option value="Blank" />
|
||||
{dealers.map((x) => (
|
||||
<option key={x.name} value={x.name}>
|
||||
{x.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</dd>
|
||||
)}
|
||||
{(!isTier5 || viewInfo.account.parentAccountName) && (
|
||||
<dd>{viewInfo.account.parentAccountName ?? "-"}</dd>
|
||||
)}
|
||||
<dt>
|
||||
{t(getTranslationID("accountPage.label.dealerManagement"))}
|
||||
</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>
|
||||
)}
|
||||
{!isTier5 && <dd>-</dd>}
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dl className={styles.listVertical}>
|
||||
<h4 className={styles.listHeader}>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"accountPage.label.administratorInformation"
|
||||
)
|
||||
)}
|
||||
</h4>
|
||||
<dt>
|
||||
{t(
|
||||
getTranslationID("accountPage.label.primaryAdministrator")
|
||||
)}
|
||||
</dt>
|
||||
<dd>
|
||||
{
|
||||
users.find(
|
||||
(x) => x.id === viewInfo.account.primaryAdminUserId
|
||||
)?.name
|
||||
}
|
||||
</dd>
|
||||
<dt>
|
||||
{t(getTranslationID("accountPage.label.emailAddress"))}
|
||||
</dt>
|
||||
<dd className={styles.form}>
|
||||
<select
|
||||
name=""
|
||||
className={`${styles.formInput} ${styles.required}`}
|
||||
onChange={(event) => {
|
||||
dispatch(
|
||||
changePrimaryAdministrator({
|
||||
primaryAdminUserId:
|
||||
users.find((x) => x.email === event.target.value)
|
||||
?.id || 0,
|
||||
})
|
||||
);
|
||||
}}
|
||||
>
|
||||
<option
|
||||
value={
|
||||
users.find(
|
||||
(x) => x.id === viewInfo.account.primaryAdminUserId
|
||||
)?.email || undefined
|
||||
}
|
||||
>
|
||||
{
|
||||
users.find(
|
||||
(x) => x.id === viewInfo.account.primaryAdminUserId
|
||||
)?.email
|
||||
}
|
||||
</option>
|
||||
{users.map((x) => (
|
||||
<option key={x.email} value={x.email}>
|
||||
{x.email}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{isPushSaveChangesButton && isEmptyPrimaryAdmin && (
|
||||
<span className={styles.formError}>
|
||||
{" "}
|
||||
{t(
|
||||
getTranslationID("signupPage.message.inputEmptyError")
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</dd>
|
||||
<dt>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"accountPage.label.secondaryAdministrator"
|
||||
)
|
||||
)}
|
||||
</dt>
|
||||
<dd>
|
||||
{
|
||||
users.find(
|
||||
(x) => x.id === viewInfo.account.secondryAdminUserId
|
||||
)?.name
|
||||
}
|
||||
</dd>
|
||||
<dt>
|
||||
{t(getTranslationID("accountPage.label.emailAddress"))}
|
||||
</dt>
|
||||
<dd className={styles.form}>
|
||||
<select
|
||||
name=""
|
||||
className={`${styles.formInput} ${styles.required}`}
|
||||
onChange={(event) => {
|
||||
dispatch(
|
||||
changeSecondryAdministrator({
|
||||
secondryAdminUserId:
|
||||
users.find((x) => x.email === event.target.value)
|
||||
?.id ?? undefined,
|
||||
})
|
||||
);
|
||||
}}
|
||||
>
|
||||
<option
|
||||
value={
|
||||
viewInfo.account.secondryAdminUserId
|
||||
? users.find(
|
||||
(x) =>
|
||||
x.id === viewInfo.account.secondryAdminUserId
|
||||
)?.email
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{viewInfo.account.secondryAdminUserId
|
||||
? users.find(
|
||||
(x) =>
|
||||
x.id === viewInfo.account.secondryAdminUserId
|
||||
)?.email
|
||||
: t(
|
||||
getTranslationID(
|
||||
"accountPage.label.selectSecondaryAdministrator"
|
||||
)
|
||||
)}
|
||||
</option>
|
||||
{/* eslint-disable-next-line jsx-a11y/control-has-associated-label */}
|
||||
<option value="Blank" />
|
||||
{users.map((x) => (
|
||||
<option key={x.email} value={x.email}>
|
||||
{x.email}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div className={`${styles.box100} ${styles.alignLeft} `}>
|
||||
<input
|
||||
type="submit"
|
||||
name="submit"
|
||||
value={t(getTranslationID("accountPage.label.saveChanges"))}
|
||||
className={`${styles.formSubmit} ${
|
||||
!isLoading ? styles.isActive : ""
|
||||
}
|
||||
`}
|
||||
onClick={onSaveChangesButton}
|
||||
/>
|
||||
<img
|
||||
style={{ display: isLoading ? "inline" : "none" }}
|
||||
src={progress_activit}
|
||||
className={styles.icLoading}
|
||||
alt="Loading"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul className={styles.linkBottom}>
|
||||
<li>
|
||||
<a href="" className={styles.linkTx}>
|
||||
{t(getTranslationID("accountPage.label.deleteAccount"))}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccountPage;
|
||||
@ -16,7 +16,7 @@ const LicensePage: React.FC = (): JSX.Element => {
|
||||
|
||||
const redirectToTopPage = useCallback(() => {
|
||||
dispatch(clearToken());
|
||||
instance.logout({
|
||||
instance.logoutRedirect({
|
||||
postLogoutRedirectUri: "/",
|
||||
});
|
||||
}, [dispatch, instance]);
|
||||
|
||||
@ -25,7 +25,7 @@ const LoginPage: React.FC = (): JSX.Element => {
|
||||
|
||||
// ログイン失敗した場合、B2Cをログアウトしてからエラーページに遷移する
|
||||
if (meta.requestStatus === "rejected") {
|
||||
instance.logout({
|
||||
instance.logoutRedirect({
|
||||
postLogoutRedirectUri: "/AuthError",
|
||||
});
|
||||
}
|
||||
|
||||
@ -24,7 +24,6 @@ import personAdd from "../../assets/images/person_add.svg";
|
||||
import { TIERS } from "../../components/auth/constants";
|
||||
import { AddPartnerAccountPopup } from "./addPartnerAccountPopup";
|
||||
import checkFill from "../../assets/images/check_fill.svg";
|
||||
import checkOutline from "../../assets/images/check_outline.svg";
|
||||
|
||||
const PartnerPage: React.FC = (): JSX.Element => {
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
|
||||
@ -20,7 +20,7 @@ const SamplePage: React.FC = (): JSX.Element => {
|
||||
type="button"
|
||||
className={styles.buttonText}
|
||||
onClick={() => {
|
||||
instance.logout({ postLogoutRedirectUri: "/" });
|
||||
instance.logoutRedirect({ postLogoutRedirectUri: "/" });
|
||||
dispatch(clearToken());
|
||||
}}
|
||||
>
|
||||
|
||||
@ -14,8 +14,10 @@ import {
|
||||
changeWorktypeId,
|
||||
changeDescription,
|
||||
listWorktypesAsync,
|
||||
updateActiveWorktypeAsync,
|
||||
selectIsLoading,
|
||||
selectWorktypes,
|
||||
selectActiveWorktypeId,
|
||||
} from "features/workflow/worktype";
|
||||
import { AppDispatch } from "app/store";
|
||||
import { AddWorktypeIdPopup } from "./addWorktypeIdPopup";
|
||||
@ -27,6 +29,8 @@ const WorktypeIdSettingPage: React.FC = (): JSX.Element => {
|
||||
const [t] = useTranslation();
|
||||
const isLoading = useSelector(selectIsLoading);
|
||||
const worktypes = useSelector(selectWorktypes);
|
||||
const activeWorktypeId = useSelector(selectActiveWorktypeId);
|
||||
|
||||
const [selectedRow, setSelectedRow] = useState<number>(NaN);
|
||||
// 追加Popupの表示制御
|
||||
const [isShowAddPopup, setIsShowAddPopup] = useState<boolean>(false);
|
||||
@ -34,10 +38,53 @@ const WorktypeIdSettingPage: React.FC = (): JSX.Element => {
|
||||
const [isShowEditPopup, setIsShowEditPopup] = useState<boolean>(false);
|
||||
const [isShowEditOptionItemPopup, setIsShowEditOptionItemPopup] =
|
||||
useState<boolean>(false);
|
||||
|
||||
// ActiveWorktypeIDのセレクトボックス表示の値
|
||||
const [selectedActiveWorktypeId, setSelectedActiveWorktypeId] = useState<
|
||||
number | undefined
|
||||
>(undefined);
|
||||
|
||||
// 初期表示
|
||||
useEffect(() => {
|
||||
dispatch(listWorktypesAsync());
|
||||
}, [dispatch]);
|
||||
|
||||
// APIから取得したactiveWorktypeIdを画面表示に反映
|
||||
useEffect(() => {
|
||||
setSelectedActiveWorktypeId(activeWorktypeId);
|
||||
}, [activeWorktypeId]);
|
||||
|
||||
// ActiveWorktypeIDのセレクトボックス変更時(セレクトボックスに表示するActiveWorktypeIDの変更時)
|
||||
useEffect(() => {
|
||||
// 画面表示の変更後にダイアログを表示するため、setTimeoutを使用
|
||||
const timeout = setTimeout(async () => {
|
||||
if (selectedActiveWorktypeId === activeWorktypeId) {
|
||||
return;
|
||||
}
|
||||
// ダイアログ確認
|
||||
if (
|
||||
// eslint-disable-next-line no-alert
|
||||
!window.confirm(t(getTranslationID("common.message.dialogConfirm")))
|
||||
) {
|
||||
setSelectedActiveWorktypeId(activeWorktypeId);
|
||||
return;
|
||||
}
|
||||
const { meta } = await dispatch(
|
||||
updateActiveWorktypeAsync({ id: selectedActiveWorktypeId })
|
||||
);
|
||||
|
||||
if (meta.requestStatus === "fulfilled") {
|
||||
dispatch(listWorktypesAsync());
|
||||
} else {
|
||||
setSelectedActiveWorktypeId(activeWorktypeId);
|
||||
}
|
||||
}, 0);
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedActiveWorktypeId]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<AddWorktypeIdPopup
|
||||
@ -109,7 +156,18 @@ const WorktypeIdSettingPage: React.FC = (): JSX.Element => {
|
||||
"worktypeIdSetting.label.activeWorktypeId"
|
||||
)
|
||||
)}:`}
|
||||
<select name="Active Worktype" className={styles.formInput}>
|
||||
<select
|
||||
name="Active Worktype"
|
||||
className={styles.formInput}
|
||||
value={selectedActiveWorktypeId ?? ""}
|
||||
onChange={(e) => {
|
||||
const { value } = e.target;
|
||||
const active = value === "" ? undefined : Number(value);
|
||||
setSelectedActiveWorktypeId(active);
|
||||
}}
|
||||
>
|
||||
{/* eslint-disable-next-line jsx-a11y/control-has-associated-label */}
|
||||
<option value="" />
|
||||
{worktypes?.map((worktype) => (
|
||||
<option key={worktype.id} value={worktype.id}>
|
||||
{worktype.worktypeId}
|
||||
|
||||
@ -537,6 +537,9 @@ h3 + .brCrumb .tlIcon {
|
||||
letter-spacing: 0.07rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
.formList dt.formTitle.alignCenter {
|
||||
text-align: center;
|
||||
}
|
||||
.formList dt.overLine {
|
||||
padding: 0 4% 0;
|
||||
line-height: 1.4;
|
||||
@ -677,6 +680,7 @@ h3 + .brCrumb .tlIcon {
|
||||
line-height: 1.4;
|
||||
letter-spacing: 0;
|
||||
font-weight: normal;
|
||||
white-space: pre-line;
|
||||
}
|
||||
.formError {
|
||||
display: block;
|
||||
@ -686,6 +690,7 @@ h3 + .brCrumb .tlIcon {
|
||||
line-height: 1.4;
|
||||
letter-spacing: 0;
|
||||
font-weight: normal;
|
||||
white-space: pre-line;
|
||||
}
|
||||
.formConfirm {
|
||||
width: 350px;
|
||||
@ -722,6 +727,26 @@ h3 + .brCrumb .tlIcon {
|
||||
.formSubmit.isActive:hover {
|
||||
background: rgba(0, 94, 184, 0.7);
|
||||
}
|
||||
.formButtonFul {
|
||||
min-width: 15rem;
|
||||
padding: 0.8rem 0.8rem;
|
||||
border: 1px #999999 solid;
|
||||
background: #ffffff;
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
letter-spacing: 0.04rem;
|
||||
font-weight: normal;
|
||||
cursor: pointer;
|
||||
border-radius: 0.3rem;
|
||||
position: relative;
|
||||
-moz-transition: all 0.3s ease-out;
|
||||
-ms-transition: all 0.3s ease-out;
|
||||
-webkit-transition: all 0.3s ease-out;
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
.formButtonFul:hover {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
.formButton {
|
||||
padding: 0.8rem 0.8rem;
|
||||
border: 1px #999999 solid;
|
||||
@ -741,6 +766,29 @@ h3 + .brCrumb .tlIcon {
|
||||
.formButton:hover {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
.formDelete {
|
||||
min-width: 15rem;
|
||||
padding: 0.8rem 2rem;
|
||||
border: 1px #999999 solid;
|
||||
color: #ffffff;
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
letter-spacing: 0.04rem;
|
||||
font-weight: normal;
|
||||
background: #e60000;
|
||||
border: 1px #e60000 solid;
|
||||
opacity: 1;
|
||||
border-radius: 0.3rem;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
-moz-transition: all 0.3s ease-out;
|
||||
-ms-transition: all 0.3s ease-out;
|
||||
-webkit-transition: all 0.3s ease-out;
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
.formDelete:hover {
|
||||
background: rgba(230, 0, 0, 0.7);
|
||||
}
|
||||
.formBack {
|
||||
width: 15rem;
|
||||
padding: 0.8rem 2rem;
|
||||
@ -780,6 +828,11 @@ h3 + .brCrumb .tlIcon {
|
||||
.formDone {
|
||||
width: 100px;
|
||||
}
|
||||
.formTrash {
|
||||
width: 50px;
|
||||
filter: brightness(0) saturate(100%) invert(10%) sepia(97%) saturate(7447%)
|
||||
hue-rotate(17deg) brightness(95%) contrast(117%);
|
||||
}
|
||||
|
||||
.listVertical {
|
||||
width: 600px;
|
||||
@ -797,6 +850,7 @@ h3 + .brCrumb .tlIcon {
|
||||
.listVertical dt,
|
||||
.listVertical dd {
|
||||
padding: 0.8rem 4%;
|
||||
background: #ffffff;
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
letter-spacing: 0.04rem;
|
||||
@ -814,6 +868,9 @@ h3 + .brCrumb .tlIcon {
|
||||
.listVertical dd {
|
||||
width: 42%;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.boxFlex {
|
||||
@ -875,7 +932,7 @@ h3 + .brCrumb .tlIcon {
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
position: fixed;
|
||||
z-index: 5;
|
||||
z-index: 6;
|
||||
}
|
||||
.modal.isShow .modalBox {
|
||||
display: block;
|
||||
@ -986,8 +1043,8 @@ h3 + .brCrumb .tlIcon {
|
||||
left: calc(50% - 25px);
|
||||
}
|
||||
.modal .form .tableWrap {
|
||||
max-height: 60vh;
|
||||
overflow-y: scroll;
|
||||
max-height: 100%;
|
||||
overflow-x: hidden;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.modal .form .table {
|
||||
@ -1074,6 +1131,7 @@ h3 + .brCrumb .tlIcon {
|
||||
border-radius: 0.2rem;
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
background: #ffffff;
|
||||
}
|
||||
.pagenationNav a.isActive {
|
||||
opacity: 1;
|
||||
@ -1157,6 +1215,9 @@ _:-ms-lang(x)::-ms-backdrop,
|
||||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.table tr:not(.tableHeader) {
|
||||
background: #ffffff;
|
||||
}
|
||||
.table tr:nth-child(2n + 3) {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
@ -1735,6 +1796,10 @@ _:-ms-lang(x)::-ms-backdrop,
|
||||
tr.isSelected .menuInTable li a {
|
||||
color: #ffffff;
|
||||
}
|
||||
tr.isSelected .menuInTable li a.isDisable {
|
||||
pointer-events: none;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.icCheckCircle {
|
||||
width: 20px;
|
||||
@ -1746,6 +1811,64 @@ tr.isSelected .menuInTable li a {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.wrap.manage .header,
|
||||
.wrap.manage .main {
|
||||
background: #def5fd;
|
||||
}
|
||||
|
||||
.manageInfo {
|
||||
width: 800px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
padding: 0.2rem 1.5rem;
|
||||
color: #0084b2;
|
||||
border: 1px #0084b2 solid;
|
||||
border-radius: 0.3rem;
|
||||
background: #def5fd;
|
||||
position: absolute;
|
||||
top: 0.2rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
box-sizing: border-box;
|
||||
z-index: 5;
|
||||
-moz-transition: all 0.3s ease-out;
|
||||
-ms-transition: all 0.3s ease-out;
|
||||
-webkit-transition: all 0.3s ease-out;
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
.manage .txNormal {
|
||||
width: calc(800px - 3rem - 3.3rem);
|
||||
font-size: 16px;
|
||||
line-height: 1.7;
|
||||
letter-spacing: 0;
|
||||
font-weight: normal;
|
||||
padding-top: 0.5rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.manage .txNormal span {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: 600;
|
||||
}
|
||||
.manageIcon {
|
||||
width: 1.5rem;
|
||||
margin-right: 1rem;
|
||||
vertical-align: middle;
|
||||
filter: brightness(0) saturate(100%) invert(31%) sepia(75%) saturate(1954%)
|
||||
hue-rotate(172deg) brightness(90%) contrast(101%);
|
||||
}
|
||||
.manageIconClose {
|
||||
width: 1.5rem;
|
||||
filter: brightness(0) saturate(100%) invert(31%) sepia(75%) saturate(1954%)
|
||||
hue-rotate(172deg) brightness(90%) contrast(101%);
|
||||
}
|
||||
.manageIconClose:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.license .listVertical dd img[src*="circle"] {
|
||||
filter: brightness(0) saturate(100%) invert(58%) sepia(41%) saturate(5814%)
|
||||
hue-rotate(143deg) brightness(96%) contrast(101%);
|
||||
@ -1987,6 +2110,12 @@ tr.isSelected .menuInTable li a {
|
||||
padding-bottom: 2rem;
|
||||
vertical-align: top;
|
||||
}
|
||||
.dictation .table.dictation td .menuInTable li:nth-child(3) {
|
||||
border-right: none;
|
||||
}
|
||||
.dictation .table.dictation td .menuInTable li a.mnBack {
|
||||
margin-left: 3rem;
|
||||
}
|
||||
.dictation .table.dictation td:has(img[alt="encrypted"]) {
|
||||
text-align: center;
|
||||
}
|
||||
@ -2127,6 +2256,44 @@ tr.isSelected .menuInTable li a {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.formList.property .formTitle {
|
||||
padding: 1rem 4% 0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.formList.property dt:not(.formTitle) {
|
||||
width: 30%;
|
||||
padding: 0 4% 0 4%;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.formList.property dt:not(.formTitle):nth-of-type(odd) {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
.formList.property dt:not(.formTitle):nth-of-type(odd) + dd {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
.formList.property dd {
|
||||
width: 58%;
|
||||
padding: 0.2rem 4% 0.2rem 0;
|
||||
margin-bottom: 0;
|
||||
white-space: pre-line;
|
||||
word-wrap: break-word;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.formList.property dd img {
|
||||
height: 1.1rem;
|
||||
}
|
||||
.formList.property dd.full {
|
||||
width: 100%;
|
||||
padding: 0.2rem 4% 0.2rem 4%;
|
||||
}
|
||||
.formList.property dd.full .buttonText {
|
||||
padding: 0 0 0.8rem;
|
||||
}
|
||||
.formList.property dd.full .buttonText img {
|
||||
vertical-align: text-bottom;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.formList dd.formChange {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@ -2165,7 +2332,8 @@ tr.isSelected .menuInTable li a {
|
||||
}
|
||||
.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,
|
||||
@ -2176,8 +2344,8 @@ tr.isSelected .menuInTable li a {
|
||||
}
|
||||
.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 {
|
||||
@ -2234,12 +2402,6 @@ tr.isSelected .menuInTable li a {
|
||||
.partners .table.partner.role4 {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
.partners .table.partner.role4 td {
|
||||
padding-bottom: 0.7rem;
|
||||
}
|
||||
.partners .table.partner.role4 .menuInTable {
|
||||
display: none;
|
||||
}
|
||||
.partners .table.partner:not(.role4) tr th:last-of-type,
|
||||
.partners .table.partner:not(.role4) tr td:last-of-type {
|
||||
display: none;
|
||||
@ -2336,7 +2498,8 @@ tr.isSelected .menuInTable li a {
|
||||
}
|
||||
.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,
|
||||
@ -2347,8 +2510,8 @@ tr.isSelected .menuInTable li a {
|
||||
}
|
||||
.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 {
|
||||
@ -2415,8 +2578,6 @@ tr.isSelected .menuInTable li a {
|
||||
margin-top: 5rem;
|
||||
padding: 0 2rem;
|
||||
font-size: 14px;
|
||||
position: absolute;
|
||||
bottom: 3rem;
|
||||
}
|
||||
|
||||
.borderTop {
|
||||
|
||||
@ -209,5 +209,6 @@ declare const classNames: {
|
||||
readonly txNormal: "txNormal";
|
||||
readonly txIcon: "txIcon";
|
||||
readonly txWswrap: "txWswrap";
|
||||
readonly required: "required";
|
||||
};
|
||||
export = classNames;
|
||||
|
||||
@ -409,7 +409,8 @@
|
||||
"worktypeIDLimitError": "(de)Worktype IDが登録件数の上限に達しているため追加できません。",
|
||||
"optionItemInvalidError": "(de)Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。",
|
||||
"optionItemSaveFailedError": "(de)オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください",
|
||||
"optionItemIncorrectError": "(de)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください"
|
||||
"optionItemIncorrectError": "(de)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください",
|
||||
"updateActiveWorktypeFailedError": "(de)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください"
|
||||
}
|
||||
},
|
||||
"partnerPage": {
|
||||
@ -426,5 +427,29 @@
|
||||
"partners": "(de)partners",
|
||||
"deleteAccount": "(de)Delete Account"
|
||||
}
|
||||
},
|
||||
"accountPage": {
|
||||
"label": {
|
||||
"title": "(de)Account",
|
||||
"fileDeleteSetting": "(de)File Delete Setting",
|
||||
"accountInformation": "(de)Account Information",
|
||||
"companyName": "(de)Company Name",
|
||||
"accountID": "(de)Account ID",
|
||||
"yourCategory": "(de)Your Category",
|
||||
"yourCountry": "(de)Your Country",
|
||||
"yourDealer": "(de)Your Dealer(Upper layer)",
|
||||
"selectDealer": "(de)Select Dealer",
|
||||
"dealerManagement": "(de)Dealer Management",
|
||||
"administratorInformation": "(de)Administrator Information",
|
||||
"primaryAdministrator": "(de)Primary Administrator",
|
||||
"secondaryAdministrator": "(de)Secondary Administrator",
|
||||
"emailAddress": "(de)E-mail address",
|
||||
"selectSecondaryAdministrator": "(de)Select Secondary Administrator",
|
||||
"saveChanges": "(de)Save Changes",
|
||||
"deleteAccount": "(de)Delete Account"
|
||||
},
|
||||
"message": {
|
||||
"updateAccountFailedError": "(de)アカウント情報の保存に失敗しました。画面を更新し、再度実行してください"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -409,7 +409,8 @@
|
||||
"worktypeIDLimitError": "Worktype IDが登録件数の上限に達しているため追加できません。",
|
||||
"optionItemInvalidError": "Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。",
|
||||
"optionItemSaveFailedError": "オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください",
|
||||
"optionItemIncorrectError": "入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください"
|
||||
"optionItemIncorrectError": "入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください",
|
||||
"updateActiveWorktypeFailedError": "Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください"
|
||||
}
|
||||
},
|
||||
"partnerPage": {
|
||||
@ -426,5 +427,29 @@
|
||||
"partners": "partners",
|
||||
"deleteAccount": "Delete Account"
|
||||
}
|
||||
},
|
||||
"accountPage": {
|
||||
"label": {
|
||||
"title": "Account",
|
||||
"fileDeleteSetting": "File Delete Setting",
|
||||
"accountInformation": "Account Information",
|
||||
"companyName": "Company Name",
|
||||
"accountID": "Account ID",
|
||||
"yourCategory": "Your Category",
|
||||
"yourCountry": "Your Country",
|
||||
"yourDealer": "Your Dealer(Upper layer)",
|
||||
"selectDealer": "Select Dealer",
|
||||
"dealerManagement": "Dealer Management",
|
||||
"administratorInformation": "Administrator Information",
|
||||
"primaryAdministrator": "Primary Administrator",
|
||||
"secondaryAdministrator": "Secondary Administrator",
|
||||
"emailAddress": "E-mail address",
|
||||
"selectSecondaryAdministrator": "Select Secondary Administrator",
|
||||
"saveChanges": "Save Changes",
|
||||
"deleteAccount": "Delete Account"
|
||||
},
|
||||
"message": {
|
||||
"updateAccountFailedError": "アカウント情報の保存に失敗しました。画面を更新し、再度実行してください"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -409,7 +409,8 @@
|
||||
"worktypeIDLimitError": "(es)Worktype IDが登録件数の上限に達しているため追加できません。",
|
||||
"optionItemInvalidError": "(es)Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。",
|
||||
"optionItemSaveFailedError": "(es)オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください",
|
||||
"optionItemIncorrectError": "(es)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください"
|
||||
"optionItemIncorrectError": "(es)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください",
|
||||
"updateActiveWorktypeFailedError": "(es)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください"
|
||||
}
|
||||
},
|
||||
"partnerPage": {
|
||||
@ -426,5 +427,29 @@
|
||||
"partners": "(es)partners",
|
||||
"deleteAccount": "(es)Delete Account"
|
||||
}
|
||||
},
|
||||
"accountPage": {
|
||||
"label": {
|
||||
"title": "(es)Account",
|
||||
"fileDeleteSetting": "(es)File Delete Setting",
|
||||
"accountInformation": "(es)Account Information",
|
||||
"companyName": "(es)Company Name",
|
||||
"accountID": "(es)Account ID",
|
||||
"yourCategory": "(es)Your Category",
|
||||
"yourCountry": "(es)Your Country",
|
||||
"yourDealer": "(es)Your Dealer(Upper layer)",
|
||||
"selectDealer": "(es)Select Dealer",
|
||||
"dealerManagement": "(es)Dealer Management",
|
||||
"administratorInformation": "(es)Administrator Information",
|
||||
"primaryAdministrator": "(es)Primary Administrator",
|
||||
"secondaryAdministrator": "(es)Secondary Administrator",
|
||||
"emailAddress": "(es)E-mail address",
|
||||
"selectSecondaryAdministrator": "(es)Select Secondary Administrator",
|
||||
"saveChanges": "(es)Save Changes",
|
||||
"deleteAccount": "(es)Delete Account"
|
||||
},
|
||||
"message": {
|
||||
"updateAccountFailedError": "(es)アカウント情報の保存に失敗しました。画面を更新し、再度実行してください"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -409,7 +409,8 @@
|
||||
"worktypeIDLimitError": "(fr)Worktype IDが登録件数の上限に達しているため追加できません。",
|
||||
"optionItemInvalidError": "(fr)Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。",
|
||||
"optionItemSaveFailedError": "(fr)オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください",
|
||||
"optionItemIncorrectError": "(fr)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください"
|
||||
"optionItemIncorrectError": "(fr)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください",
|
||||
"updateActiveWorktypeFailedError": "(fr)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください"
|
||||
}
|
||||
},
|
||||
"partnerPage": {
|
||||
@ -426,5 +427,29 @@
|
||||
"partners": "(fr)partners",
|
||||
"deleteAccount": "(fr)Delete Account"
|
||||
}
|
||||
},
|
||||
"accountPage": {
|
||||
"label": {
|
||||
"title": "(fr)Account",
|
||||
"fileDeleteSetting": "(fr)File Delete Setting",
|
||||
"accountInformation": "(fr)Account Information",
|
||||
"companyName": "(fr)Company Name",
|
||||
"accountID": "(fr)Account ID",
|
||||
"yourCategory": "(fr)Your Category",
|
||||
"yourCountry": "(fr)Your Country",
|
||||
"yourDealer": "(fr)Your Dealer(Upper layer)",
|
||||
"selectDealer": "(fr)Select Dealer",
|
||||
"dealerManagement": "(fr)Dealer Management",
|
||||
"administratorInformation": "(fr)Administrator Information",
|
||||
"primaryAdministrator": "(fr)Primary Administrator",
|
||||
"secondaryAdministrator": "(fr)Secondary Administrator",
|
||||
"emailAddress": "(fr)E-mail address",
|
||||
"selectSecondaryAdministrator": "(fr)Select Secondary Administrator",
|
||||
"saveChanges": "(fr)Save Changes",
|
||||
"deleteAccount": "(fr)Delete Account"
|
||||
},
|
||||
"message": {
|
||||
"updateAccountFailedError": "(fr)アカウント情報の保存に失敗しました。画面を更新し、再度実行してください"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
-- +migrate Up
|
||||
ALTER TABLE `template_files` DROP COLUMN `deleted_at`;
|
||||
|
||||
-- +migrate Down
|
||||
ALTER TABLE `template_files` ADD COLUMN `deleted_at` TIMESTAMP COMMENT '削除時刻' AFTER `file_name`;
|
||||
@ -226,7 +226,7 @@
|
||||
"security": [{ "bearer": [] }]
|
||||
},
|
||||
"post": {
|
||||
"operationId": "me",
|
||||
"operationId": "updateAccountInfo",
|
||||
"summary": "",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
@ -1181,6 +1181,51 @@
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/accounts/delete": {
|
||||
"post": {
|
||||
"operationId": "deleteAccount",
|
||||
"summary": "",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/DeleteAccountRequest" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功時のレスポンス",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UpdateAccountInfoResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "認証エラー",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "DBアクセスに失敗しログインできる状態で処理が終了した場合",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["accounts"],
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/users/confirm": {
|
||||
"post": {
|
||||
"operationId": "confirmUser",
|
||||
@ -1849,6 +1894,100 @@
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/files/template/upload-location": {
|
||||
"get": {
|
||||
"operationId": "uploadTemplateLocation",
|
||||
"summary": "",
|
||||
"description": "ログイン中ユーザー用のBlob Storage上のテンプレートファイルのアップロード先アクセスURLを取得します",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功時のレスポンス",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/TemplateUploadLocationResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "認証エラー",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "想定外のサーバーエラー",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["files"],
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/files/template/upload-finished": {
|
||||
"post": {
|
||||
"operationId": "uploadTemplateFinished",
|
||||
"summary": "",
|
||||
"description": "アップロードが完了したテンプレートファイルの情報を登録します",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/TemplateUploadFinishedRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功時のレスポンス",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/TemplateUploadFinishedReqponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "不正なパラメータ",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "認証エラー",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "想定外のサーバーエラー",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["files"],
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/tasks": {
|
||||
"get": {
|
||||
"operationId": "getTasks",
|
||||
@ -2671,6 +2810,44 @@
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/templates": {
|
||||
"get": {
|
||||
"operationId": "getTemplates",
|
||||
"summary": "",
|
||||
"description": "アカウント内のテンプレートファイルの一覧を取得します",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功時のレスポンス",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetTemplatesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "認証エラー",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "想定外のサーバーエラー",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["templates"],
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/notification/register": {
|
||||
"post": {
|
||||
"operationId": "register",
|
||||
@ -2843,7 +3020,8 @@
|
||||
"parentAccountId": { "type": "number" },
|
||||
"delegationPermission": { "type": "boolean" },
|
||||
"primaryAdminUserId": { "type": "number" },
|
||||
"secondryAdminUserId": { "type": "number" }
|
||||
"secondryAdminUserId": { "type": "number" },
|
||||
"parentAccountName": { "type": "string" }
|
||||
},
|
||||
"required": [
|
||||
"accountId",
|
||||
@ -3263,9 +3441,16 @@
|
||||
"description": "セカンダリ管理者ID"
|
||||
}
|
||||
},
|
||||
"required": ["delegationPermission"]
|
||||
"required": ["delegationPermission", "primaryAdminUserId"]
|
||||
},
|
||||
"UpdateAccountInfoResponse": { "type": "object", "properties": {} },
|
||||
"DeleteAccountRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accountId": { "type": "number", "description": "アカウントID" }
|
||||
},
|
||||
"required": ["accountId"]
|
||||
},
|
||||
"ConfirmRequest": {
|
||||
"type": "object",
|
||||
"properties": { "token": { "type": "string" } },
|
||||
@ -3602,6 +3787,36 @@
|
||||
"properties": { "url": { "type": "string" } },
|
||||
"required": ["url"]
|
||||
},
|
||||
"TemplateUploadLocationResponse": {
|
||||
"type": "object",
|
||||
"properties": { "url": { "type": "string" } },
|
||||
"required": ["url"]
|
||||
},
|
||||
"TemplateFile": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "テンプレートファイルのファイル名"
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "テンプレートファイルのURL"
|
||||
}
|
||||
},
|
||||
"required": ["name", "url"]
|
||||
},
|
||||
"TemplateUploadFinishedRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"templateFile": {
|
||||
"description": "テンプレートファイルのファイル情報",
|
||||
"allOf": [{ "$ref": "#/components/schemas/TemplateFile" }]
|
||||
}
|
||||
},
|
||||
"required": ["templateFile"]
|
||||
},
|
||||
"TemplateUploadFinishedReqponse": { "type": "object", "properties": {} },
|
||||
"Assignee": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -3808,6 +4023,17 @@
|
||||
"required": ["poNumber"]
|
||||
},
|
||||
"CancelOrderResponse": { "type": "object", "properties": {} },
|
||||
"GetTemplatesResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"templates": {
|
||||
"description": "テンプレートファイルの一覧",
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/components/schemas/TemplateFile" }
|
||||
}
|
||||
},
|
||||
"required": ["templates"]
|
||||
},
|
||||
"RegisterRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@ -28,6 +28,8 @@ import { NotificationModule } from './features/notification/notification.module'
|
||||
import { FilesModule } from './features/files/files.module';
|
||||
import { FilesController } from './features/files/files.controller';
|
||||
import { FilesService } from './features/files/files.service';
|
||||
import { TemplatesModule } from './features/templates/templates.module';
|
||||
import { TemplatesController } from './features/templates/templates.controller';
|
||||
import { TasksService } from './features/tasks/tasks.service';
|
||||
import { TasksController } from './features/tasks/tasks.controller';
|
||||
import { TasksModule } from './features/tasks/tasks.module';
|
||||
@ -41,6 +43,7 @@ import { UserGroupsRepositoryModule } from './repositories/user_groups/user_grou
|
||||
import { SortCriteriaRepositoryModule } from './repositories/sort_criteria/sort_criteria.repository.module';
|
||||
import { TemplateFilesRepositoryModule } from './repositories/template_files/template_files.repository.module';
|
||||
import { WorktypesRepositoryModule } from './repositories/worktypes/worktypes.repository.module';
|
||||
import { TemplatesService } from './features/templates/templates.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -65,6 +68,7 @@ import { WorktypesRepositoryModule } from './repositories/worktypes/worktypes.re
|
||||
FilesModule,
|
||||
TasksModule,
|
||||
UsersModule,
|
||||
TemplatesModule,
|
||||
SendGridModule,
|
||||
LicensesModule,
|
||||
AccountsRepositoryModule,
|
||||
@ -106,6 +110,7 @@ import { WorktypesRepositoryModule } from './repositories/worktypes/worktypes.re
|
||||
TasksController,
|
||||
UsersController,
|
||||
LicensesController,
|
||||
TemplatesController,
|
||||
],
|
||||
providers: [
|
||||
AuthService,
|
||||
@ -115,6 +120,7 @@ import { WorktypesRepositoryModule } from './repositories/worktypes/worktypes.re
|
||||
FilesService,
|
||||
TasksService,
|
||||
LicensesService,
|
||||
TemplatesService,
|
||||
],
|
||||
})
|
||||
export class AppModule {
|
||||
|
||||
@ -36,6 +36,7 @@ export const ErrorCodes = [
|
||||
'E010302', // authorId重複エラー
|
||||
'E010401', // PONumber重複エラー
|
||||
'E010501', // アカウント不在エラー
|
||||
'E010502', // アカウント情報変更不可エラー
|
||||
'E010601', // タスク変更不可エラー(タスクが変更できる状態でない、またはタスクが存在しない)
|
||||
'E010602', // タスク変更権限不足エラー
|
||||
'E010603', // タスク不在エラー
|
||||
|
||||
@ -25,6 +25,7 @@ export const errors: Errors = {
|
||||
E010302: 'This AuthorId already used Error',
|
||||
E010401: 'This PoNumber already used Error',
|
||||
E010501: 'Account not Found Error.',
|
||||
E010502: 'Account information cannot be changed Error.',
|
||||
E010601: 'Task is not Editable Error',
|
||||
E010602: 'No task edit permissions Error',
|
||||
E010603: 'Task not found Error.',
|
||||
|
||||
@ -60,6 +60,8 @@ import {
|
||||
PostActiveWorktypeResponse,
|
||||
UpdateAccountInfoRequest,
|
||||
UpdateAccountInfoResponse,
|
||||
DeleteAccountRequest,
|
||||
DeleteAccountResponse,
|
||||
} from './types/types';
|
||||
import { USER_ROLES, ADMIN_ROLES, TIERS } from '../../constants';
|
||||
import { AuthGuard } from '../../common/guards/auth/authguards';
|
||||
@ -909,9 +911,7 @@ export class AccountsController {
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
console.log('id', id);
|
||||
console.log(context.trackingId);
|
||||
|
||||
await this.accountService.updateActiveWorktype(context, userId, id);
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -985,7 +985,7 @@ export class AccountsController {
|
||||
description: '想定外のサーバーエラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiOperation({ operationId: 'me' })
|
||||
@ApiOperation({ operationId: 'updateAccountInfo' })
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(
|
||||
@ -1004,19 +1004,61 @@ export class AccountsController {
|
||||
secondryAdminUserId,
|
||||
} = body;
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const { userId, tier } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const context = makeContext(userId);
|
||||
// 仮。API実装で本実装
|
||||
// await this.accountService.updateAccountInfo(
|
||||
// context,
|
||||
// userId,
|
||||
// parentAccountId,
|
||||
// delegationPermission,
|
||||
// primaryAdminUserId,
|
||||
// secondryAdminUserId,
|
||||
// );
|
||||
|
||||
await this.accountService.updateAccountInfo(
|
||||
context,
|
||||
userId,
|
||||
tier,
|
||||
delegationPermission,
|
||||
primaryAdminUserId,
|
||||
parentAccountId,
|
||||
secondryAdminUserId,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@Post('/delete')
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: UpdateAccountInfoResponse,
|
||||
description: '成功時のレスポンス',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.UNAUTHORIZED,
|
||||
description: '認証エラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
description: 'DBアクセスに失敗しログインできる状態で処理が終了した場合',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiOperation({ operationId: 'deleteAccount' })
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(
|
||||
RoleGuard.requireds({
|
||||
roles: [ADMIN_ROLES.ADMIN],
|
||||
}),
|
||||
)
|
||||
async deleteAccount(
|
||||
@Req() req: Request,
|
||||
@Body() body: DeleteAccountRequest,
|
||||
): Promise<DeleteAccountResponse> {
|
||||
const { accountId } = body;
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const context = makeContext(userId);
|
||||
|
||||
/* TODO 仮実装、別タスクで実装する
|
||||
await this.accountService.deleteAccount(
|
||||
context,
|
||||
accountId
|
||||
);
|
||||
*/
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,6 +65,7 @@ import {
|
||||
import { WorktypesRepositoryService } from '../../repositories/worktypes/worktypes.repository.service';
|
||||
import { AdB2cUser } from '../../gateways/adb2c/types/types';
|
||||
import { Worktype } from '../../repositories/worktypes/entity/worktype.entity';
|
||||
import { AccountsRepositoryService } from '../../repositories/accounts/accounts.repository.service';
|
||||
|
||||
describe('createAccount', () => {
|
||||
let source: DataSource = null;
|
||||
@ -4322,6 +4323,221 @@ describe('updateOptionItems', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateActiveWorktype', () => {
|
||||
let source: DataSource = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
database: ':memory:',
|
||||
logging: false,
|
||||
entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
|
||||
synchronize: true, // trueにすると自動的にmigrationが行われるため注意
|
||||
});
|
||||
return source.initialize();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('アカウントのActiveWorktypeIDを指定WorktypeIDに更新できる(NULL⇒ID設定)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
const worktype = await createWorktype(source, account.id, 'worktype1');
|
||||
|
||||
//作成したデータを確認
|
||||
{
|
||||
const beforeAccount = await getAccount(source, account.id);
|
||||
expect(beforeAccount.active_worktype_id).toBe(null);
|
||||
}
|
||||
|
||||
await service.updateActiveWorktype(context, admin.external_id, worktype.id);
|
||||
|
||||
//実行結果を確認
|
||||
{
|
||||
const { active_worktype_id } = await getAccount(source, account.id);
|
||||
expect(active_worktype_id).toBe(worktype.id);
|
||||
}
|
||||
});
|
||||
|
||||
it('アカウントのActiveWorktypeIDを指定WorktypeIDに更新できる(別のWorkTypeIDを設定)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
const worktype1 = await createWorktype(
|
||||
source,
|
||||
account.id,
|
||||
'worktype1',
|
||||
'description1',
|
||||
true,
|
||||
);
|
||||
const worktype2 = await createWorktype(source, account.id, 'worktype2');
|
||||
|
||||
//作成したデータを確認
|
||||
{
|
||||
const beforeAccount = await getAccount(source, account.id);
|
||||
expect(beforeAccount.active_worktype_id).toBe(worktype1.id);
|
||||
}
|
||||
|
||||
await service.updateActiveWorktype(
|
||||
context,
|
||||
admin.external_id,
|
||||
worktype2.id,
|
||||
);
|
||||
|
||||
//実行結果を確認
|
||||
{
|
||||
const { active_worktype_id } = await getAccount(source, account.id);
|
||||
expect(active_worktype_id).toBe(worktype2.id);
|
||||
}
|
||||
});
|
||||
|
||||
it('アカウントのActiveWorktypeIDをNULLに更新できる(WorkTypeID⇒NULL)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
const worktype1 = await createWorktype(
|
||||
source,
|
||||
account.id,
|
||||
'worktype1',
|
||||
'description1',
|
||||
true,
|
||||
);
|
||||
|
||||
//作成したデータを確認
|
||||
{
|
||||
const beforeAccount = await getAccount(source, account.id);
|
||||
expect(beforeAccount.active_worktype_id).toBe(worktype1.id);
|
||||
}
|
||||
|
||||
await service.updateActiveWorktype(context, admin.external_id, undefined);
|
||||
|
||||
//実行結果を確認
|
||||
{
|
||||
const { active_worktype_id } = await getAccount(source, account.id);
|
||||
expect(active_worktype_id).toBe(null);
|
||||
}
|
||||
});
|
||||
|
||||
it('自アカウント内に指定されたIDのWorktypeIDが存在しない場合、400エラーとなること(WorkTypeIDが存在しない場合)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
await createWorktype(source, account.id, 'worktype1');
|
||||
|
||||
//作成したデータを確認
|
||||
{
|
||||
const beforeAccount = await getAccount(source, account.id);
|
||||
expect(beforeAccount.active_worktype_id).toBe(null);
|
||||
}
|
||||
|
||||
try {
|
||||
await service.updateActiveWorktype(context, admin.external_id, 999);
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E011003'));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('自アカウント内に指定されたIDのWorktypeIDが存在しない場合、400エラーとなること(WorkTypeIDが別アカウントの場合)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
const { account: otherAccount } = await makeTestAccount(source, {
|
||||
tier: 5,
|
||||
});
|
||||
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
await createWorktype(source, account.id, 'worktype1');
|
||||
await createWorktype(source, otherAccount.id, 'worktype2');
|
||||
|
||||
//作成したデータを確認
|
||||
{
|
||||
const beforeAccount = await getAccount(source, account.id);
|
||||
const worktype1 = await getWorktypes(source, account.id);
|
||||
const worktype2 = await getWorktypes(source, otherAccount.id);
|
||||
expect(beforeAccount.active_worktype_id).toBe(null);
|
||||
|
||||
expect(worktype1.length).toBe(1);
|
||||
expect(worktype1[0].custom_worktype_id).toBe('worktype1');
|
||||
|
||||
expect(worktype2.length).toBe(1);
|
||||
expect(worktype2[0].custom_worktype_id).toBe('worktype2');
|
||||
}
|
||||
|
||||
try {
|
||||
await service.updateActiveWorktype(context, admin.external_id, 999);
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E011003'));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('DBアクセスに失敗した場合、500エラーを返却する', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
await createWorktype(source, account.id, 'worktype1');
|
||||
|
||||
//作成したデータを確認
|
||||
{
|
||||
const beforeAccount = await getAccount(source, account.id);
|
||||
expect(beforeAccount.active_worktype_id).toBe(null);
|
||||
}
|
||||
|
||||
//DBアクセスに失敗するようにする
|
||||
const accountsRepositoryService = module.get<AccountsRepositoryService>(
|
||||
AccountsRepositoryService,
|
||||
);
|
||||
accountsRepositoryService.updateActiveWorktypeId = jest
|
||||
.fn()
|
||||
.mockRejectedValue('DB failed');
|
||||
|
||||
try {
|
||||
await service.updateActiveWorktype(context, admin.external_id, 999);
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('ライセンス発行キャンセル', () => {
|
||||
let source: DataSource = null;
|
||||
beforeEach(async () => {
|
||||
@ -4758,6 +4974,179 @@ describe('パートナー一覧取得', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('アカウント情報更新', () => {
|
||||
let source: DataSource = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
database: ':memory:',
|
||||
logging: false,
|
||||
entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
|
||||
synchronize: true, // trueにすると自動的にmigrationが行われるため注意
|
||||
});
|
||||
return source.initialize();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
it('アカウント情報を更新する(第五階層が実行/セカンダリ管理者ユーザがnull)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
|
||||
source,
|
||||
);
|
||||
const tier5Accounts = await makeTestAccount(source, {
|
||||
parent_account_id: tier4Accounts[0].account.id,
|
||||
tier: 5,
|
||||
});
|
||||
await service.updateAccountInfo(
|
||||
makeContext('trackingId'),
|
||||
tier5Accounts.admin.external_id,
|
||||
tier5Accounts.account.tier,
|
||||
true,
|
||||
tier5Accounts.admin.id,
|
||||
tier4Accounts[0].account.id,
|
||||
undefined,
|
||||
);
|
||||
|
||||
// DB内が想定通りになっているか確認
|
||||
const account = await getAccount(source, tier5Accounts.account.id);
|
||||
expect(account.parent_account_id).toBe(tier4Accounts[0].account.id);
|
||||
expect(account.delegation_permission).toBe(true);
|
||||
expect(account.primary_admin_user_id).toBe(tier5Accounts.admin.id);
|
||||
expect(account.secondary_admin_user_id).toBe(null);
|
||||
});
|
||||
it('アカウント情報を更新する(第五階層以外が実行)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const { tier3Accounts: tier3Accounts, tier4Accounts: tier4Accounts } =
|
||||
await makeHierarchicalAccounts(source);
|
||||
const adduser = await makeTestUser(source, {
|
||||
account_id: tier4Accounts[0].account.id,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: 'typist',
|
||||
});
|
||||
await service.updateAccountInfo(
|
||||
makeContext('trackingId'),
|
||||
tier4Accounts[0].users[0].external_id,
|
||||
tier4Accounts[0].account.tier,
|
||||
false,
|
||||
tier4Accounts[0].users[0].id,
|
||||
tier3Accounts[0].account.id,
|
||||
adduser.id,
|
||||
);
|
||||
|
||||
// DB内が想定通りになっているか確認
|
||||
const account = await getAccount(source, tier4Accounts[0].account.id);
|
||||
expect(account.parent_account_id).toBe(tier3Accounts[0].account.id);
|
||||
expect(account.delegation_permission).toBe(false);
|
||||
expect(account.primary_admin_user_id).toBe(tier4Accounts[0].users[0].id);
|
||||
expect(account.secondary_admin_user_id).toBe(adduser.id);
|
||||
});
|
||||
it('アカウント情報を更新する(ディーラーアカウントが未入力)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const { tier3Accounts: tier3Accounts, tier4Accounts: tier4Accounts } =
|
||||
await makeHierarchicalAccounts(source);
|
||||
const adduser = await makeTestUser(source, {
|
||||
account_id: tier4Accounts[0].account.id,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: 'typist',
|
||||
});
|
||||
await service.updateAccountInfo(
|
||||
makeContext('trackingId'),
|
||||
tier4Accounts[0].users[0].external_id,
|
||||
tier4Accounts[0].account.tier,
|
||||
false,
|
||||
tier4Accounts[0].users[0].id,
|
||||
undefined,
|
||||
adduser.id,
|
||||
);
|
||||
|
||||
// DB内が想定通りになっているか確認
|
||||
const account = await getAccount(source, tier4Accounts[0].account.id);
|
||||
expect(account.parent_account_id).toBe(null);
|
||||
expect(account.delegation_permission).toBe(false);
|
||||
expect(account.primary_admin_user_id).toBe(tier4Accounts[0].users[0].id);
|
||||
expect(account.secondary_admin_user_id).toBe(adduser.id);
|
||||
});
|
||||
it('アカウント情報の更新に失敗する(ディーラー未存在)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
|
||||
source,
|
||||
);
|
||||
const adduser = await makeTestUser(source, {
|
||||
account_id: tier4Accounts[0].account.id,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: 'typist',
|
||||
});
|
||||
await expect(
|
||||
service.updateAccountInfo(
|
||||
makeContext('trackingId'),
|
||||
tier4Accounts[0].users[0].external_id,
|
||||
tier4Accounts[0].account.tier,
|
||||
false,
|
||||
tier4Accounts[0].users[0].id,
|
||||
123,
|
||||
adduser.id,
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010502'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
it('アカウント情報の更新に失敗する(プライマリ管理者ユーザ未存在)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
|
||||
source,
|
||||
);
|
||||
const tier5Accounts = await makeTestAccount(source, {
|
||||
parent_account_id: tier4Accounts[0].account.id,
|
||||
tier: 5,
|
||||
});
|
||||
await expect(
|
||||
service.updateAccountInfo(
|
||||
makeContext('trackingId'),
|
||||
tier5Accounts.admin.external_id,
|
||||
tier5Accounts.account.tier,
|
||||
true,
|
||||
999,
|
||||
tier4Accounts[0].account.id,
|
||||
tier4Accounts[1].users[0].id,
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010502'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
it('アカウント情報の更新に失敗する(セカンダリ管理者ユーザ未存在)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
|
||||
source,
|
||||
);
|
||||
const tier5Accounts = await makeTestAccount(source, {
|
||||
parent_account_id: tier4Accounts[0].account.id,
|
||||
tier: 5,
|
||||
});
|
||||
await expect(
|
||||
service.updateAccountInfo(
|
||||
makeContext('trackingId'),
|
||||
tier5Accounts.admin.external_id,
|
||||
tier5Accounts.account.tier,
|
||||
true,
|
||||
tier4Accounts[0].users[0].id,
|
||||
tier4Accounts[0].account.id,
|
||||
999,
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010502'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAccountInfo', () => {
|
||||
let source: DataSource = null;
|
||||
beforeEach(async () => {
|
||||
@ -4775,12 +5164,14 @@ describe('getAccountInfo', () => {
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('パラメータのユーザに対応するアカウント情報を取得できる', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
|
||||
source,
|
||||
);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, {
|
||||
parent_account_id: 123,
|
||||
parent_account_id: tier4Accounts[0].account.id,
|
||||
});
|
||||
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
@ -4807,6 +5198,9 @@ describe('getAccountInfo', () => {
|
||||
);
|
||||
expect(accountResponse.account.secondryAdminUserId).toBe(undefined);
|
||||
expect(accountResponse.account.tier).toBe(account.tier);
|
||||
expect(accountResponse.account.parentAccountName).toBe(
|
||||
tier4Accounts[0].account.company_name,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -37,12 +37,15 @@ import {
|
||||
ExpirationThresholdDate,
|
||||
} from '../licenses/types/types';
|
||||
import { GetLicenseSummaryResponse, Typist } from './types/types';
|
||||
import { AccessToken } from '../../common/token';
|
||||
import { UserNotFoundError } from '../../repositories/users/errors/types';
|
||||
import { UserGroupsRepositoryService } from '../../repositories/user_groups/user_groups.repository.service';
|
||||
import { makePassword } from '../../common/password';
|
||||
import { LicensesRepositoryService } from '../../repositories/licenses/licenses.repository.service';
|
||||
import { AccountNotFoundError } from '../../repositories/accounts/errors/types';
|
||||
import {
|
||||
AccountNotFoundError,
|
||||
AdminUserNotFoundError,
|
||||
DealerAccountNotFoundError,
|
||||
} from '../../repositories/accounts/errors/types';
|
||||
import { Context } from '../../common/log';
|
||||
import {
|
||||
LicensesShortageError,
|
||||
@ -385,6 +388,13 @@ export class AccountsService {
|
||||
userInfo.account_id,
|
||||
);
|
||||
|
||||
let parentInfo: Account;
|
||||
if (accountInfo.parent_account_id) {
|
||||
parentInfo = await this.accountRepository.findAccountById(
|
||||
accountInfo.parent_account_id,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
account: {
|
||||
accountId: userInfo.account_id,
|
||||
@ -395,6 +405,7 @@ export class AccountsService {
|
||||
delegationPermission: accountInfo.delegation_permission,
|
||||
primaryAdminUserId: accountInfo.primary_admin_user_id ?? undefined,
|
||||
secondryAdminUserId: accountInfo.secondary_admin_user_id ?? undefined,
|
||||
parentAccountName: parentInfo ? parentInfo.company_name : undefined,
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
@ -1473,6 +1484,58 @@ export class AccountsService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ActiveWorktypeの更新
|
||||
* @param context
|
||||
* @param externalId
|
||||
* @param id ActiveWorktypeの内部ID
|
||||
* @returns active worktype
|
||||
*/
|
||||
async updateActiveWorktype(
|
||||
context: Context,
|
||||
externalId: string,
|
||||
id: number,
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.updateActiveWorktype.name} | params: { ` +
|
||||
`externalId: ${externalId}, ` +
|
||||
`id: ${id} };`,
|
||||
);
|
||||
try {
|
||||
// 外部IDをもとにユーザー情報を取得する
|
||||
const { account_id: accountId } =
|
||||
await this.usersRepository.findUserByExternalId(externalId);
|
||||
|
||||
// ActiveWorktypeを更新する
|
||||
await this.accountRepository.updateActiveWorktypeId(accountId, id);
|
||||
} catch (e) {
|
||||
this.logger.error(e);
|
||||
if (e instanceof Error) {
|
||||
switch (e.constructor) {
|
||||
// 内部IDで指定されたWorktypeが存在しない場合は400エラーを返す
|
||||
case WorktypeIdNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E011003'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
default:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.trackingId}] ${this.updateActiveWorktype.name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* パートナー一覧を取得します
|
||||
* @param context
|
||||
@ -1553,4 +1616,72 @@ export class AccountsService {
|
||||
this.logger.log(`[OUT] [${context.trackingId}] ${this.getPartners.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* アカウント情報を設定する
|
||||
* @param context
|
||||
* @param externalId
|
||||
* @param tier
|
||||
* @param delegationPermission
|
||||
* @param primaryAdminUserId
|
||||
* @param parentAccountId
|
||||
* @param secondryAdminUserId
|
||||
* @returns UpdateAccountInfoResponse
|
||||
*/
|
||||
async updateAccountInfo(
|
||||
context: Context,
|
||||
externalId: string,
|
||||
tier: number,
|
||||
delegationPermission: boolean,
|
||||
primaryAdminUserId: number,
|
||||
parentAccountId?: number,
|
||||
secondryAdminUserId?: number,
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.updateAccountInfo.name} | params: { ` +
|
||||
`externalId: ${externalId}, ` +
|
||||
`delegationPermission: ${delegationPermission}, ` +
|
||||
`primaryAdminUserId: ${primaryAdminUserId}, ` +
|
||||
`parentAccountId: ${parentAccountId}, ` +
|
||||
`secondryAdminUserId: ${secondryAdminUserId}, };`,
|
||||
);
|
||||
|
||||
try {
|
||||
const { account_id: accountId } =
|
||||
await this.usersRepository.findUserByExternalId(externalId);
|
||||
await this.accountRepository.updateAccountInfo(
|
||||
accountId,
|
||||
tier,
|
||||
delegationPermission,
|
||||
primaryAdminUserId,
|
||||
parentAccountId,
|
||||
secondryAdminUserId,
|
||||
);
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.trackingId}] error=${e}`);
|
||||
if (e instanceof Error) {
|
||||
switch (e.constructor) {
|
||||
case DealerAccountNotFoundError:
|
||||
case AdminUserNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010502'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
default:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.trackingId}] ${this.updateAccountInfo.name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,6 +124,9 @@ export class Account {
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
secondryAdminUserId?: number | undefined;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
parentAccountName?: string | undefined;
|
||||
}
|
||||
|
||||
export class GetMyAccountResponse {
|
||||
@ -547,12 +550,18 @@ export class UpdateAccountInfoRequest {
|
||||
parentAccountId?: number | undefined;
|
||||
@ApiProperty({ description: '代行操作許可' })
|
||||
delegationPermission: boolean;
|
||||
@ApiProperty({ description: 'プライマリ管理者ID', required: false })
|
||||
@IsOptional()
|
||||
primaryAdminUserId?: number | undefined;
|
||||
@ApiProperty({ description: 'プライマリ管理者ID' })
|
||||
primaryAdminUserId: number;
|
||||
@ApiProperty({ description: 'セカンダリ管理者ID', required: false })
|
||||
@IsOptional()
|
||||
secondryAdminUserId?: number | undefined;
|
||||
}
|
||||
|
||||
export class UpdateAccountInfoResponse {}
|
||||
|
||||
export class DeleteAccountRequest {
|
||||
@ApiProperty({ description: 'アカウントID' })
|
||||
accountId: number;
|
||||
}
|
||||
|
||||
export class DeleteAccountResponse {}
|
||||
|
||||
@ -27,10 +27,13 @@ import {
|
||||
AudioUploadLocationResponse,
|
||||
TemplateDownloadLocationRequest,
|
||||
TemplateDownloadLocationResponse,
|
||||
TemplateUploadFinishedReqponse,
|
||||
TemplateUploadFinishedRequest,
|
||||
TemplateUploadLocationResponse,
|
||||
} from './types/types';
|
||||
import { AuthGuard } from '../../common/guards/auth/authguards';
|
||||
import { RoleGuard } from '../../common/guards/role/roleguards';
|
||||
import { USER_ROLES } from '../../constants';
|
||||
import { ADMIN_ROLES, USER_ROLES } from '../../constants';
|
||||
import { retrieveAuthorizationToken } from '../../common/http/helper';
|
||||
import { Request } from 'express';
|
||||
import { makeContext } from '../../common/log';
|
||||
@ -258,4 +261,84 @@ export class FilesController {
|
||||
|
||||
return { url };
|
||||
}
|
||||
|
||||
@Get('template/upload-location')
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: TemplateUploadLocationResponse,
|
||||
description: '成功時のレスポンス',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.UNAUTHORIZED,
|
||||
description: '認証エラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
description: '想定外のサーバーエラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiOperation({
|
||||
operationId: 'uploadTemplateLocation',
|
||||
description:
|
||||
'ログイン中ユーザー用のBlob Storage上のテンプレートファイルのアップロード先アクセスURLを取得します',
|
||||
})
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
|
||||
async uploadTemplateLocation(
|
||||
@Req() req: Request,
|
||||
): Promise<TemplateUploadLocationResponse> {
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const accessToken = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const context = makeContext(accessToken.userId);
|
||||
|
||||
console.log(context.trackingId);
|
||||
|
||||
return { url: '' };
|
||||
}
|
||||
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: TemplateUploadFinishedReqponse,
|
||||
description: '成功時のレスポンス',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.BAD_REQUEST,
|
||||
description: '不正なパラメータ',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.UNAUTHORIZED,
|
||||
description: '認証エラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
description: '想定外のサーバーエラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiOperation({
|
||||
operationId: 'uploadTemplateFinished',
|
||||
description: 'アップロードが完了したテンプレートファイルの情報を登録します',
|
||||
})
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
|
||||
@Post('template/upload-finished')
|
||||
async templateUploadFinished(
|
||||
@Req() req: Request,
|
||||
@Body() body: TemplateUploadFinishedRequest,
|
||||
): Promise<TemplateUploadFinishedReqponse> {
|
||||
const { templateFile } = body;
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const accessToken = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const context = makeContext(accessToken.userId);
|
||||
console.log(context.trackingId);
|
||||
console.log(templateFile);
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { TemplateFile } from '../../templates/types/types';
|
||||
|
||||
export class AudioUploadLocationRequest {}
|
||||
|
||||
@ -31,6 +32,11 @@ export class TemplateDownloadLocationResponse {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export class TemplateUploadLocationResponse {
|
||||
@ApiProperty()
|
||||
url: string;
|
||||
}
|
||||
|
||||
export class AudioOptionItem {
|
||||
@ApiProperty({ minLength: 1, maxLength: 16 })
|
||||
optionItemLabel: string;
|
||||
@ -87,3 +93,10 @@ export class AudioUploadFinishedResponse {
|
||||
@ApiProperty({ description: '8桁固定の数字' })
|
||||
jobNumber: string;
|
||||
}
|
||||
|
||||
export class TemplateUploadFinishedRequest {
|
||||
@ApiProperty({ description: 'テンプレートファイルのファイル情報' })
|
||||
templateFile: TemplateFile;
|
||||
}
|
||||
|
||||
export class TemplateUploadFinishedReqponse {}
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { TemplatesController } from './templates.controller';
|
||||
import { TemplatesService } from './templates.service';
|
||||
|
||||
describe('TemplatesController', () => {
|
||||
let controller: TemplatesController;
|
||||
const mockTemplatesService = {};
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
envFilePath: ['.env.local', '.env'],
|
||||
isGlobal: true,
|
||||
}),
|
||||
],
|
||||
controllers: [TemplatesController],
|
||||
providers: [TemplatesService],
|
||||
})
|
||||
.overrideProvider(TemplatesService)
|
||||
.useValue(mockTemplatesService)
|
||||
.compile();
|
||||
|
||||
controller = module.get<TemplatesController>(TemplatesController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,57 @@
|
||||
import { Controller, Get, HttpStatus, Req, UseGuards } from '@nestjs/common';
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
ApiOperation,
|
||||
ApiResponse,
|
||||
ApiTags,
|
||||
} from '@nestjs/swagger';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { AccessToken } from '../../common/token';
|
||||
import { ErrorResponse } from '../../common/error/types/types';
|
||||
import { GetTemplatesResponse } from './types/types';
|
||||
import { AuthGuard } from '../../common/guards/auth/authguards';
|
||||
import { RoleGuard } from '../../common/guards/role/roleguards';
|
||||
import { ADMIN_ROLES } from '../../constants';
|
||||
import { retrieveAuthorizationToken } from '../../common/http/helper';
|
||||
import { Request } from 'express';
|
||||
import { makeContext } from '../../common/log';
|
||||
import { TemplatesService } from './templates.service';
|
||||
|
||||
@ApiTags('templates')
|
||||
@Controller('templates')
|
||||
export class TemplatesController {
|
||||
constructor(private readonly templatesService: TemplatesService) {}
|
||||
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: GetTemplatesResponse,
|
||||
description: '成功時のレスポンス',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.UNAUTHORIZED,
|
||||
description: '認証エラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
description: '想定外のサーバーエラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiOperation({
|
||||
operationId: 'getTemplates',
|
||||
description: 'アカウント内のテンプレートファイルの一覧を取得します',
|
||||
})
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
|
||||
@Get()
|
||||
async getTemplates(@Req() req: Request): Promise<GetTemplatesResponse> {
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const accessToken = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const context = makeContext(accessToken.userId);
|
||||
console.log(context.trackingId);
|
||||
|
||||
return { templates: [] };
|
||||
}
|
||||
}
|
||||
10
dictation_server/src/features/templates/templates.module.ts
Normal file
10
dictation_server/src/features/templates/templates.module.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TemplatesController } from './templates.controller';
|
||||
import { TemplatesService } from './templates.service';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
providers: [TemplatesService],
|
||||
controllers: [TemplatesController],
|
||||
})
|
||||
export class TemplatesModule {}
|
||||
@ -0,0 +1,7 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class TemplatesService {
|
||||
private readonly logger = new Logger(TemplatesService.name);
|
||||
constructor() {}
|
||||
}
|
||||
16
dictation_server/src/features/templates/types/types.ts
Normal file
16
dictation_server/src/features/templates/types/types.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class TemplateFile {
|
||||
@ApiProperty({ description: 'テンプレートファイルのファイル名' })
|
||||
name: string;
|
||||
@ApiProperty({ description: 'テンプレートファイルのURL' })
|
||||
url: string;
|
||||
}
|
||||
|
||||
export class GetTemplatesResponse {
|
||||
@ApiProperty({
|
||||
description: 'テンプレートファイルの一覧',
|
||||
type: [TemplateFile],
|
||||
})
|
||||
templates: TemplateFile[];
|
||||
}
|
||||
@ -29,13 +29,19 @@ import {
|
||||
PartnerLicenseInfoForRepository,
|
||||
PartnerInfoFromDb,
|
||||
} from '../../features/accounts/types/types';
|
||||
import { AccountNotFoundError } from './errors/types';
|
||||
import {
|
||||
AccountNotFoundError,
|
||||
AdminUserNotFoundError,
|
||||
DealerAccountNotFoundError,
|
||||
} from './errors/types';
|
||||
import {
|
||||
AlreadyLicenseAllocatedError,
|
||||
AlreadyLicenseStatusChangedError,
|
||||
CancellationPeriodExpiredError,
|
||||
} from '../licenses/errors/types';
|
||||
import { DateWithZeroTime } from '../../features/licenses/types/types';
|
||||
import { Worktype } from '../worktypes/entity/worktype.entity';
|
||||
import { WorktypeIdNotFoundError } from '../worktypes/errors/types';
|
||||
|
||||
@Injectable()
|
||||
export class AccountsRepositoryService {
|
||||
@ -765,4 +771,135 @@ export class AccountsRepositoryService {
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 一階層上のアカウントを取得する
|
||||
* @param accountId
|
||||
* @param tier
|
||||
* @returns account: 一階層上のアカウント
|
||||
*/
|
||||
async getOneUpperTierAccount(
|
||||
accountId: number,
|
||||
tier: number,
|
||||
): Promise<Account | undefined> {
|
||||
return await this.dataSource.transaction(async (entityManager) => {
|
||||
const accountRepo = entityManager.getRepository(Account);
|
||||
return await accountRepo.findOne({
|
||||
where: {
|
||||
id: accountId,
|
||||
tier: tier - 1,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* アカウント情報を保存する
|
||||
* @param myAccountId
|
||||
* @param tier
|
||||
* @param delegationPermission
|
||||
* @param primaryAdminUserId
|
||||
* @param parentAccountId
|
||||
* @param secondryAdminUserId
|
||||
*/
|
||||
async updateAccountInfo(
|
||||
myAccountId: number,
|
||||
tier: number,
|
||||
delegationPermission: boolean,
|
||||
primaryAdminUserId: number,
|
||||
parentAccountId?: number,
|
||||
secondryAdminUserId?: number,
|
||||
): Promise<void> {
|
||||
await this.dataSource.transaction(async (entityManager) => {
|
||||
// ディーラーアカウントが指定されている場合、存在チェックを行う
|
||||
if (parentAccountId) {
|
||||
const dealerAccount = await this.getOneUpperTierAccount(
|
||||
parentAccountId,
|
||||
tier,
|
||||
);
|
||||
// 取得できない場合、エラー
|
||||
if (!dealerAccount) {
|
||||
throw new DealerAccountNotFoundError(
|
||||
`Dealer account is not found. id: ${parentAccountId}}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const userRepo = entityManager.getRepository(User);
|
||||
// プライマリ管理者ユーザーの存在チェック
|
||||
if (primaryAdminUserId) {
|
||||
const primaryAdminUser = await userRepo.findOne({
|
||||
where: {
|
||||
id: primaryAdminUserId,
|
||||
account_id: myAccountId,
|
||||
},
|
||||
});
|
||||
if (!primaryAdminUser) {
|
||||
throw new AdminUserNotFoundError(
|
||||
`Primary admin user is not found. id: ${primaryAdminUserId}, account_id: ${myAccountId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// セカンダリ管理者ユーザーの存在チェック
|
||||
if (secondryAdminUserId) {
|
||||
const secondryAdminUser = await userRepo.findOne({
|
||||
where: {
|
||||
id: secondryAdminUserId,
|
||||
account_id: myAccountId,
|
||||
},
|
||||
});
|
||||
if (!secondryAdminUser) {
|
||||
throw new AdminUserNotFoundError(
|
||||
`Secondry admin user is not found. id: ${secondryAdminUserId}, account_id: ${myAccountId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
const accountRepo = entityManager.getRepository(Account);
|
||||
// アカウント情報を更新
|
||||
await accountRepo.update(
|
||||
{ id: myAccountId },
|
||||
{
|
||||
parent_account_id: parentAccountId || null,
|
||||
delegation_permission: delegationPermission,
|
||||
primary_admin_user_id: primaryAdminUserId,
|
||||
secondary_admin_user_id: secondryAdminUserId || null,
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* ActiveWorktypeIdを更新する
|
||||
* @param accountId
|
||||
* @param [id] ActiveWorktypeIdの内部ID
|
||||
* @returns active worktype id
|
||||
*/
|
||||
async updateActiveWorktypeId(
|
||||
accountId: number,
|
||||
id?: number | undefined,
|
||||
): Promise<void> {
|
||||
return await this.dataSource.transaction(async (entityManager) => {
|
||||
const worktypeRepo = entityManager.getRepository(Worktype);
|
||||
const accountRepo = entityManager.getRepository(Account);
|
||||
|
||||
if (id) {
|
||||
// 自アカウント内に指定IDのワークタイプが存在するか確認
|
||||
const worktype = await worktypeRepo.findOne({
|
||||
where: { account_id: accountId, id: id },
|
||||
});
|
||||
|
||||
// ワークタイプが存在しない場合はエラー
|
||||
if (!worktype) {
|
||||
throw new WorktypeIdNotFoundError('Worktype is not found. id: ${id}');
|
||||
}
|
||||
}
|
||||
|
||||
// アカウントのActiveWorktypeIDを更新
|
||||
await accountRepo.update(
|
||||
{ id: accountId },
|
||||
{ active_worktype_id: id ?? null },
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,2 +1,6 @@
|
||||
// アカウント未発見エラー
|
||||
export class AccountNotFoundError extends Error {}
|
||||
// ディーラーアカウント未存在エラー
|
||||
export class DealerAccountNotFoundError extends Error {}
|
||||
// 管理者ユーザ未存在エラー
|
||||
export class AdminUserNotFoundError extends Error {}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user