Merged PR 367: 画面実装(注文履歴画面修正)
## 概要 [Task2497: 画面実装(注文履歴画面修正)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2497) - 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず) - 何をどう変更したか、追加したライブラリなど - 注文履歴画面に対して、Issue Cancel押下時にライセンス発行キャンセルAPIを呼び出す処理を追加しました - Issue Cancelボタンの表示条件を追加し、自分が第一または第二階層、かつ対象が第五階層の場合に表示するよう対応しました - このPull Requestでの対象/対象外 - api.tsおよび言語毎のメッセージファイルに一部別機能のものが含まれています - 影響範囲(他の機能にも影響があるか) ## レビューポイント - 特にレビューしてほしい箇所 - Issue Cancelボタンの表示条件について、2行程度ですが外出ししています。変であればコメントいただければと思います。 ## UIの変更 - 変更なしです ## 動作確認状況 - ローカルで確認済です ## 補足 - 相談、参考資料などがあれば
This commit is contained in:
parent
cb7ba77bc3
commit
9a67c513d9
@ -870,6 +870,19 @@ export interface GetUsersResponse {
|
||||
*/
|
||||
'users': Array<User>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface GetWorkTypesResponse
|
||||
*/
|
||||
export interface GetWorkTypesResponse {
|
||||
/**
|
||||
*
|
||||
* @type {Array<WorkType>}
|
||||
* @memberof GetWorkTypesResponse
|
||||
*/
|
||||
'workTypes': Array<WorkType>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@ -1593,6 +1606,31 @@ export interface User {
|
||||
*/
|
||||
'licenseStatus': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface WorkType
|
||||
*/
|
||||
export interface WorkType {
|
||||
/**
|
||||
* WorkTypeのID
|
||||
* @type {number}
|
||||
* @memberof WorkType
|
||||
*/
|
||||
'id': number;
|
||||
/**
|
||||
* WorkTypeID
|
||||
* @type {string}
|
||||
* @memberof WorkType
|
||||
*/
|
||||
'workTypeId': string;
|
||||
/**
|
||||
* WorkTypeの説明
|
||||
* @type {string}
|
||||
* @memberof WorkType
|
||||
*/
|
||||
'description'?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* AccountsApi - axios parameter creator
|
||||
@ -2037,6 +2075,40 @@ export const AccountsApiAxiosParamCreator = function (configuration?: Configurat
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getWorktypes: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/accounts/worktypes`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
// authentication bearer required
|
||||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
@ -2087,7 +2159,7 @@ export const AccountsApiAxiosParamCreator = function (configuration?: Configurat
|
||||
};
|
||||
},
|
||||
/**
|
||||
* ログインしているユーザーのアカウント配下でIDで指定されたタイピストグループを編集します
|
||||
* ログインしているユーザーのアカウント配下でIDで指定されたタイピストグループを更新します
|
||||
* @summary
|
||||
* @param {number} typistGroupId
|
||||
* @param {UpdateTypistGroupRequest} updateTypistGroupRequest
|
||||
@ -2268,6 +2340,16 @@ export const AccountsApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getTypists(options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async getWorktypes(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<GetWorkTypesResponse>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getWorktypes(options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
@ -2280,7 +2362,7 @@ export const AccountsApiFp = function(configuration?: Configuration) {
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
* ログインしているユーザーのアカウント配下でIDで指定されたタイピストグループを編集します
|
||||
* ログインしているユーザーのアカウント配下でIDで指定されたタイピストグループを更新します
|
||||
* @summary
|
||||
* @param {number} typistGroupId
|
||||
* @param {UpdateTypistGroupRequest} updateTypistGroupRequest
|
||||
@ -2417,6 +2499,15 @@ export const AccountsApiFactory = function (configuration?: Configuration, baseP
|
||||
getTypists(options?: any): AxiosPromise<GetTypistsResponse> {
|
||||
return localVarFp.getTypists(options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getWorktypes(options?: any): AxiosPromise<GetWorkTypesResponse> {
|
||||
return localVarFp.getWorktypes(options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
@ -2428,7 +2519,7 @@ export const AccountsApiFactory = function (configuration?: Configuration, baseP
|
||||
return localVarFp.issueLicense(issueLicenseRequest, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* ログインしているユーザーのアカウント配下でIDで指定されたタイピストグループを編集します
|
||||
* ログインしているユーザーのアカウント配下でIDで指定されたタイピストグループを更新します
|
||||
* @summary
|
||||
* @param {number} typistGroupId
|
||||
* @param {UpdateTypistGroupRequest} updateTypistGroupRequest
|
||||
@ -2588,6 +2679,17 @@ export class AccountsApi extends BaseAPI {
|
||||
return AccountsApiFp(this.configuration).getTypists(options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof AccountsApi
|
||||
*/
|
||||
public getWorktypes(options?: AxiosRequestConfig) {
|
||||
return AccountsApiFp(this.configuration).getWorktypes(options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
@ -2601,7 +2703,7 @@ export class AccountsApi extends BaseAPI {
|
||||
}
|
||||
|
||||
/**
|
||||
* ログインしているユーザーのアカウント配下でIDで指定されたタイピストグループを編集します
|
||||
* ログインしているユーザーのアカウント配下でIDで指定されたタイピストグループを更新します
|
||||
* @summary
|
||||
* @param {number} typistGroupId
|
||||
* @param {UpdateTypistGroupRequest} updateTypistGroupRequest
|
||||
|
||||
@ -48,4 +48,7 @@ export const errorCodes = [
|
||||
"E010806", // ライセンス割り当て不可エラー
|
||||
"E010807", // ライセンス割り当て解除不可エラー
|
||||
"E010808", // ライセンス注文キャンセル不可エラー
|
||||
"E010809", // ライセンス発行キャンセル不可エラー(ステータスが変えられている場合)
|
||||
"E010810", // ライセンス発行キャンセル不可エラー(発行から一定期間経過した場合)
|
||||
"E010811", // ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合)
|
||||
] as const;
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
getLicenseOrderHistoriesAsync,
|
||||
issueLicenseAsync,
|
||||
cancelOrderAsync,
|
||||
cancelIssueAsync,
|
||||
} from "./operations";
|
||||
import { LIMIT_ORDER_HISORY_NUM } from "./constants";
|
||||
|
||||
@ -74,6 +75,15 @@ export const licenseOrderHistorySlice = createSlice({
|
||||
builder.addCase(cancelOrderAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(cancelIssueAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(cancelIssueAsync.fulfilled, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(cancelIssueAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -202,3 +202,76 @@ export const cancelOrderAsync = createAsyncThunk<
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
export const cancelIssueAsync = createAsyncThunk<
|
||||
{
|
||||
/* Empty Object */
|
||||
},
|
||||
{
|
||||
// パラメータ
|
||||
orderedAccountId: number;
|
||||
poNumber: string;
|
||||
},
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("licenses/cancelIssueAsync", async (args, thunkApi) => {
|
||||
const { orderedAccountId, poNumber } = args;
|
||||
// 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);
|
||||
|
||||
try {
|
||||
await accountsApi.cancelIssue(
|
||||
{
|
||||
orderedAccountId,
|
||||
poNumber,
|
||||
},
|
||||
{
|
||||
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");
|
||||
|
||||
if (error.code === "E000108") {
|
||||
errorMessage = getTranslationID("common.message.permissionDeniedError");
|
||||
} else if (error.code === "E010809") {
|
||||
errorMessage = getTranslationID(
|
||||
"orderHistoriesPage.message.alreadyLicenseStatusChanged"
|
||||
);
|
||||
} else if (error.code === "E010810") {
|
||||
errorMessage = getTranslationID(
|
||||
"orderHistoriesPage.message.expiredSinceIssued"
|
||||
);
|
||||
} else if (error.code === "E010811") {
|
||||
errorMessage = getTranslationID(
|
||||
"orderHistoriesPage.message.alreadyLicenseAllocated"
|
||||
);
|
||||
}
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: errorMessage,
|
||||
})
|
||||
);
|
||||
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
/* eslint-disable jsx-a11y/control-has-associated-label */
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import { UpdateTokenTimer } from "components/auth/updateTokenTimer";
|
||||
import { isApproveTier } from "features/auth/utils";
|
||||
import { TIERS } from "components/auth/constants";
|
||||
import Footer from "components/footer";
|
||||
import Header from "components/header";
|
||||
import styles from "styles/app.module.scss";
|
||||
@ -22,6 +24,7 @@ import {
|
||||
selectOffset,
|
||||
savePageInfo,
|
||||
selectCompanyName,
|
||||
cancelIssueAsync,
|
||||
} from "features/license/licenseOrderHistory";
|
||||
import { selectSelectedRow } from "features/license/partnerLicense";
|
||||
import undo from "../../assets/images/undo.svg";
|
||||
@ -107,6 +110,28 @@ export const LicenseOrderHistory: React.FC<LicenseOrderHistoryProps> = (
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
// Issue Cancelボタンを押下時の処理
|
||||
const onCancelIssue = useCallback(
|
||||
async (orderedAccountId: number, poNumber: string) => {
|
||||
// ダイアログ確認
|
||||
// eslint-disable-next-line no-alert
|
||||
if (window.confirm(t(getTranslationID("common.message.dialogConfirm")))) {
|
||||
// ライセンス発行キャンセルAPIの呼び出し
|
||||
const { meta } = await dispatch(
|
||||
cancelIssueAsync({
|
||||
orderedAccountId,
|
||||
poNumber,
|
||||
})
|
||||
);
|
||||
if (meta.requestStatus === "fulfilled") {
|
||||
UpdateHistoriesList();
|
||||
}
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
// ページネーションのボタンクリック時のアクション
|
||||
const movePage = (targetOffset: number) => {
|
||||
dispatch(
|
||||
@ -274,12 +299,21 @@ export const LicenseOrderHistory: React.FC<LicenseOrderHistoryProps> = (
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||
<a
|
||||
className={`${styles.menuLink} ${
|
||||
x.status === STATUS.ISSUED
|
||||
x.status === STATUS.ISSUED &&
|
||||
isIssueCancelVisibleTier(selectedRow.tier)
|
||||
? styles.isActive
|
||||
: ""
|
||||
}`}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
onCancelIssue(
|
||||
selectedRow.accountId,
|
||||
x.poNumber
|
||||
);
|
||||
}}
|
||||
>
|
||||
{t(
|
||||
getTranslationID(
|
||||
@ -371,4 +405,10 @@ export const LicenseOrderHistory: React.FC<LicenseOrderHistoryProps> = (
|
||||
);
|
||||
};
|
||||
|
||||
// isIssueCanceボタンが表示できる階層かどうかを判定する
|
||||
const isIssueCancelVisibleTier = (selectedRowsTier: number) =>
|
||||
// 自アカウントが階層1または2、かつ対象が階層5の場合ボタン表示
|
||||
isApproveTier([TIERS.TIER1, TIERS.TIER2]) &&
|
||||
TIERS.TIER5 === selectedRowsTier.toString();
|
||||
|
||||
export default LicenseOrderHistory;
|
||||
|
||||
@ -7,7 +7,8 @@
|
||||
"internalServerError": "(de)処理に失敗しました。時間をおいて再実行しても解決しない場合はシステム管理者にお問い合わせください。",
|
||||
"listEmpty": "(de)検索結果が0件です。",
|
||||
"dialogConfirm": "(de)操作を実行しますか?",
|
||||
"success": "(de)処理に成功しました。"
|
||||
"success": "(de)処理に成功しました。",
|
||||
"permissionDeniedError": "(de)操作を実行する権限がありません。"
|
||||
},
|
||||
"label": {
|
||||
"cancel": "(de)Cancel",
|
||||
@ -330,7 +331,10 @@
|
||||
"message": {
|
||||
"notEnoughOfNumberOfLicense": "(de)ライセンスが不足しているため、発行することができませんでした。ライセンスの注文を行ってください。",
|
||||
"alreadyIssueLicense": "(de)すでに発行済みの注文です。画面を更新してください。",
|
||||
"alreadyLicenseIssueOrCancel": "(de)ライセンス注文のキャンセルに失敗しました。選択された注文はすでに発行またはキャンセルされています。画面を更新して再度ご確認ください。"
|
||||
"alreadyLicenseIssueOrCancel": "(de)ライセンス注文のキャンセルに失敗しました。選択された注文はすでに発行またはキャンセルされています。画面を更新して再度ご確認ください。",
|
||||
"alreadyLicenseStatusChanged": "(de)ライセンス発行のキャンセルに失敗しました。選択された注文の状態が変更されています。画面を更新して再度ご確認ください。",
|
||||
"expiredSinceIssued": "(de)発行日から一定期間経過しているため、発行キャンセルできません。",
|
||||
"alreadyLicenseAllocated": "(de)発行したライセンスがすでに割り当てられたため、発行キャンセルできません。"
|
||||
}
|
||||
},
|
||||
"allocateLicensePopupPage": {
|
||||
|
||||
@ -7,7 +7,8 @@
|
||||
"internalServerError": "処理に失敗しました。時間をおいて再実行しても解決しない場合はシステム管理者にお問い合わせください。",
|
||||
"listEmpty": "検索結果が0件です。",
|
||||
"dialogConfirm": "操作を実行しますか?",
|
||||
"success": "処理に成功しました。"
|
||||
"success": "処理に成功しました。",
|
||||
"permissionDeniedError": "操作を実行する権限がありません。"
|
||||
},
|
||||
"label": {
|
||||
"cancel": "Cancel",
|
||||
@ -330,7 +331,10 @@
|
||||
"message": {
|
||||
"notEnoughOfNumberOfLicense": "ライセンスが不足しているため、発行することができませんでした。ライセンスの注文を行ってください。",
|
||||
"alreadyIssueLicense": "すでに発行済みの注文です。画面を更新してください。",
|
||||
"alreadyLicenseIssueOrCancel": "ライセンス注文のキャンセルに失敗しました。選択された注文はすでに発行またはキャンセルされています。画面を更新して再度ご確認ください。"
|
||||
"alreadyLicenseIssueOrCancel": "ライセンス注文のキャンセルに失敗しました。選択された注文はすでに発行またはキャンセルされています。画面を更新して再度ご確認ください。",
|
||||
"alreadyLicenseStatusChanged": "ライセンス発行のキャンセルに失敗しました。選択された注文の状態が変更されています。画面を更新して再度ご確認ください。",
|
||||
"expiredSinceIssued": "発行日から一定期間経過しているため、発行キャンセルできません。",
|
||||
"alreadyLicenseAllocated": "発行したライセンスがすでに割り当てられたため、発行キャンセルできません。"
|
||||
}
|
||||
},
|
||||
"allocateLicensePopupPage": {
|
||||
|
||||
@ -7,7 +7,8 @@
|
||||
"internalServerError": "(es)処理に失敗しました。時間をおいて再実行しても解決しない場合はシステム管理者にお問い合わせください。",
|
||||
"listEmpty": "(es)検索結果が0件です。",
|
||||
"dialogConfirm": "(es)操作を実行しますか?",
|
||||
"success": "(es)処理に成功しました。"
|
||||
"success": "(es)処理に成功しました。",
|
||||
"permissionDeniedError": "(es)操作を実行する権限がありません。"
|
||||
},
|
||||
"label": {
|
||||
"cancel": "(es)Cancel",
|
||||
@ -330,7 +331,10 @@
|
||||
"message": {
|
||||
"notEnoughOfNumberOfLicense": "(es)ライセンスが不足しているため、発行することができませんでした。ライセンスの注文を行ってください。",
|
||||
"alreadyIssueLicense": "(es)すでに発行済みの注文です。画面を更新してください。",
|
||||
"alreadyLicenseIssueOrCancel": "(es)ライセンス注文のキャンセルに失敗しました。選択された注文はすでに発行またはキャンセルされています。画面を更新して再度ご確認ください。"
|
||||
"alreadyLicenseIssueOrCancel": "(es)ライセンス注文のキャンセルに失敗しました。選択された注文はすでに発行またはキャンセルされています。画面を更新して再度ご確認ください。",
|
||||
"alreadyLicenseStatusChanged": "(es)ライセンス発行のキャンセルに失敗しました。選択された注文の状態が変更されています。画面を更新して再度ご確認ください。",
|
||||
"expiredSinceIssued": "(es)発行日から一定期間経過しているため、発行キャンセルできません。",
|
||||
"alreadyLicenseAllocated": "(es)発行したライセンスがすでに割り当てられたため、発行キャンセルできません。"
|
||||
}
|
||||
},
|
||||
"allocateLicensePopupPage": {
|
||||
|
||||
@ -7,7 +7,8 @@
|
||||
"internalServerError": "(fr)処理に失敗しました。時間をおいて再実行しても解決しない場合はシステム管理者にお問い合わせください。",
|
||||
"listEmpty": "(fr)検索結果が0件です。",
|
||||
"dialogConfirm": "(fr)操作を実行しますか?",
|
||||
"success": "(fr)処理に成功しました。"
|
||||
"success": "(fr)処理に成功しました。",
|
||||
"permissionDeniedError": "(fr)操作を実行する権限がありません。"
|
||||
},
|
||||
"label": {
|
||||
"cancel": "(fr)Cancel",
|
||||
@ -330,7 +331,10 @@
|
||||
"message": {
|
||||
"notEnoughOfNumberOfLicense": "(fr)ライセンスが不足しているため、発行することができませんでした。ライセンスの注文を行ってください。",
|
||||
"alreadyIssueLicense": "(fr)すでに発行済みの注文です。画面を更新してください。",
|
||||
"alreadyLicenseIssueOrCancel": "(fr)ライセンス注文のキャンセルに失敗しました。選択された注文はすでに発行またはキャンセルされています。画面を更新して再度ご確認ください。"
|
||||
"alreadyLicenseIssueOrCancel": "(fr)ライセンス注文のキャンセルに失敗しました。選択された注文はすでに発行またはキャンセルされています。画面を更新して再度ご確認ください。",
|
||||
"alreadyLicenseStatusChanged": "(fr)ライセンス発行のキャンセルに失敗しました。選択された注文の状態が変更されています。画面を更新して再度ご確認ください。",
|
||||
"expiredSinceIssued": "(fr)発行日から一定期間経過しているため、発行キャンセルできません。",
|
||||
"alreadyLicenseAllocated": "(fr)発行したライセンスがすでに割り当てられたため、発行キャンセルできません。"
|
||||
}
|
||||
},
|
||||
"allocateLicensePopupPage": {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user