Merge remote-tracking branch 'origin/develop' into main
This commit is contained in:
commit
2f5daa46f5
@ -12,6 +12,7 @@ jobs:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
persistCredentials: true
|
||||
- script: |
|
||||
git fetch origin main:main
|
||||
if git merge-base --is-ancestor $(Build.SourceVersion) main; then
|
||||
|
||||
@ -15,6 +15,7 @@ jobs:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
persistCredentials: true
|
||||
- script: |
|
||||
git fetch origin main:main
|
||||
if git merge-base --is-ancestor $(Build.SourceVersion) main; then
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
"networkInterfaces_pep_odms_app_dev_nic_6b27b52b_0703_4bfa_b69a_66b82ec6ca3e_name": {
|
||||
"type": "String"
|
||||
},
|
||||
"networkInterfaces_pep_odms_app_test_nic_e7e4687e_685e_4023_bbab_a16ccfe8822b_name": {
|
||||
"networkInterfaces_pep_odms_app_test_nic_714ca5c0_83a1_42fb_b8e4_8a2b5a2660ed_name": {
|
||||
"type": "String"
|
||||
},
|
||||
"networkInterfaces_pep_odms_staapp_dev_nic_a67c70a7_750f_47d4_9844_b82b66095ef1_name": {
|
||||
@ -436,6 +436,141 @@
|
||||
"rules": [
|
||||
{
|
||||
"ruleId": "942440"
|
||||
},
|
||||
{
|
||||
"ruleId": "942100"
|
||||
},
|
||||
{
|
||||
"ruleId": "942110"
|
||||
},
|
||||
{
|
||||
"ruleId": "942120"
|
||||
},
|
||||
{
|
||||
"ruleId": "942130"
|
||||
},
|
||||
{
|
||||
"ruleId": "942140"
|
||||
},
|
||||
{
|
||||
"ruleId": "942150"
|
||||
},
|
||||
{
|
||||
"ruleId": "942160"
|
||||
},
|
||||
{
|
||||
"ruleId": "942170"
|
||||
},
|
||||
{
|
||||
"ruleId": "942180"
|
||||
},
|
||||
{
|
||||
"ruleId": "942190"
|
||||
},
|
||||
{
|
||||
"ruleId": "942200"
|
||||
},
|
||||
{
|
||||
"ruleId": "942210"
|
||||
},
|
||||
{
|
||||
"ruleId": "942220"
|
||||
},
|
||||
{
|
||||
"ruleId": "942230"
|
||||
},
|
||||
{
|
||||
"ruleId": "942240"
|
||||
},
|
||||
{
|
||||
"ruleId": "942250"
|
||||
},
|
||||
{
|
||||
"ruleId": "942251"
|
||||
},
|
||||
{
|
||||
"ruleId": "942270"
|
||||
},
|
||||
{
|
||||
"ruleId": "942280"
|
||||
},
|
||||
{
|
||||
"ruleId": "942290"
|
||||
},
|
||||
{
|
||||
"ruleId": "942300"
|
||||
},
|
||||
{
|
||||
"ruleId": "942310"
|
||||
},
|
||||
{
|
||||
"ruleId": "942320"
|
||||
},
|
||||
{
|
||||
"ruleId": "942330"
|
||||
},
|
||||
{
|
||||
"ruleId": "942340"
|
||||
},
|
||||
{
|
||||
"ruleId": "942350"
|
||||
},
|
||||
{
|
||||
"ruleId": "942360"
|
||||
},
|
||||
{
|
||||
"ruleId": "942361"
|
||||
},
|
||||
{
|
||||
"ruleId": "942370"
|
||||
},
|
||||
{
|
||||
"ruleId": "942380"
|
||||
},
|
||||
{
|
||||
"ruleId": "942390"
|
||||
},
|
||||
{
|
||||
"ruleId": "942400"
|
||||
},
|
||||
{
|
||||
"ruleId": "942410"
|
||||
},
|
||||
{
|
||||
"ruleId": "942420"
|
||||
},
|
||||
{
|
||||
"ruleId": "942421"
|
||||
},
|
||||
{
|
||||
"ruleId": "942430"
|
||||
},
|
||||
{
|
||||
"ruleId": "942431"
|
||||
},
|
||||
{
|
||||
"ruleId": "942432"
|
||||
},
|
||||
{
|
||||
"ruleId": "942450"
|
||||
},
|
||||
{
|
||||
"ruleId": "942460"
|
||||
},
|
||||
{
|
||||
"ruleId": "942470"
|
||||
},
|
||||
{
|
||||
"ruleId": "942480"
|
||||
},
|
||||
{
|
||||
"ruleId": "942490"
|
||||
},
|
||||
{
|
||||
"ruleId": "942500"
|
||||
},
|
||||
{
|
||||
"ruleId": "942260"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1739,7 +1874,7 @@
|
||||
],
|
||||
"kind": "Regular",
|
||||
"location": "japaneast",
|
||||
"name": "[parameters('networkInterfaces_pep_odms_app_test_nic_e7e4687e_685e_4023_bbab_a16ccfe8822b_name')]",
|
||||
"name": "[parameters('networkInterfaces_pep_odms_app_test_nic_714ca5c0_83a1_42fb_b8e4_8a2b5a2660ed_name')]",
|
||||
"properties": {
|
||||
"disableTcpStateTracking": false,
|
||||
"dnsSettings": {
|
||||
@ -1749,9 +1884,9 @@
|
||||
"enableIPForwarding": false,
|
||||
"ipConfigurations": [
|
||||
{
|
||||
"etag": "W/\"4ae02394-b8c4-4949-b8c9-afa8f9a4816c\"",
|
||||
"id": "[concat(resourceId('Microsoft.Network/networkInterfaces', parameters('networkInterfaces_pep_odms_app_test_nic_e7e4687e_685e_4023_bbab_a16ccfe8822b_name')), '/ipConfigurations/privateEndpointIpConfig.2c5fae85-4959-4d63-ae7b-569ad00b2fdc')]",
|
||||
"name": "privateEndpointIpConfig.2c5fae85-4959-4d63-ae7b-569ad00b2fdc",
|
||||
"etag": "W/\"de5f333a-686a-419a-be07-4fb339cbf7b8\"",
|
||||
"id": "[concat(resourceId('Microsoft.Network/networkInterfaces', parameters('networkInterfaces_pep_odms_app_test_nic_714ca5c0_83a1_42fb_b8e4_8a2b5a2660ed_name')), '/ipConfigurations/privateEndpointIpConfig.474c2657-ac02-4810-8202-004da3c9cd93')]",
|
||||
"name": "privateEndpointIpConfig.474c2657-ac02-4810-8202-004da3c9cd93",
|
||||
"properties": {
|
||||
"primary": true,
|
||||
"privateIPAddress": "10.1.1.9",
|
||||
@ -2047,7 +2182,7 @@
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"creator": "created by private endpoint pep-odms-app-test with resource guid 78a4dbd3-7b3f-436e-a7ae-3aba5cea7341"
|
||||
"creator": "created by private endpoint pep-odms-app-test with resource guid f272f317-2526-4bbe-bfe9-18083902e925"
|
||||
},
|
||||
"ttl": 10
|
||||
},
|
||||
@ -2066,7 +2201,7 @@
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"creator": "created by private endpoint pep-odms-app-test with resource guid 78a4dbd3-7b3f-436e-a7ae-3aba5cea7341"
|
||||
"creator": "created by private endpoint pep-odms-app-test with resource guid f272f317-2526-4bbe-bfe9-18083902e925"
|
||||
},
|
||||
"ttl": 10
|
||||
},
|
||||
@ -2298,8 +2433,8 @@
|
||||
"manualPrivateLinkServiceConnections": [],
|
||||
"privateLinkServiceConnections": [
|
||||
{
|
||||
"id": "[concat(resourceId('Microsoft.Network/privateEndpoints', parameters('privateEndpoints_pep_odms_app_test_name')), concat('/privateLinkServiceConnections/', parameters('privateEndpoints_pep_odms_app_test_name'), '-81c1'))]",
|
||||
"name": "[concat(parameters('privateEndpoints_pep_odms_app_test_name'), '-81c1')]",
|
||||
"id": "[concat(resourceId('Microsoft.Network/privateEndpoints', parameters('privateEndpoints_pep_odms_app_test_name')), concat('/privateLinkServiceConnections/', parameters('privateEndpoints_pep_odms_app_test_name'), '-bd85'))]",
|
||||
"name": "[concat(parameters('privateEndpoints_pep_odms_app_test_name'), '-bd85')]",
|
||||
"properties": {
|
||||
"groupIds": [
|
||||
"sites"
|
||||
@ -11148,6 +11283,10 @@
|
||||
"properties": {}
|
||||
}
|
||||
],
|
||||
"sslPolicy": {
|
||||
"policyName": "AppGwSslPolicy20220101",
|
||||
"policyType": "Predefined"
|
||||
},
|
||||
"sslProfiles": [],
|
||||
"trustedClientCertificates": [],
|
||||
"trustedRootCertificates": [],
|
||||
|
||||
@ -414,6 +414,141 @@
|
||||
"rules": [
|
||||
{
|
||||
"ruleId": "942440"
|
||||
},
|
||||
{
|
||||
"ruleId": "942100"
|
||||
},
|
||||
{
|
||||
"ruleId": "942110"
|
||||
},
|
||||
{
|
||||
"ruleId": "942120"
|
||||
},
|
||||
{
|
||||
"ruleId": "942130"
|
||||
},
|
||||
{
|
||||
"ruleId": "942140"
|
||||
},
|
||||
{
|
||||
"ruleId": "942150"
|
||||
},
|
||||
{
|
||||
"ruleId": "942160"
|
||||
},
|
||||
{
|
||||
"ruleId": "942170"
|
||||
},
|
||||
{
|
||||
"ruleId": "942180"
|
||||
},
|
||||
{
|
||||
"ruleId": "942190"
|
||||
},
|
||||
{
|
||||
"ruleId": "942200"
|
||||
},
|
||||
{
|
||||
"ruleId": "942210"
|
||||
},
|
||||
{
|
||||
"ruleId": "942220"
|
||||
},
|
||||
{
|
||||
"ruleId": "942230"
|
||||
},
|
||||
{
|
||||
"ruleId": "942240"
|
||||
},
|
||||
{
|
||||
"ruleId": "942250"
|
||||
},
|
||||
{
|
||||
"ruleId": "942251"
|
||||
},
|
||||
{
|
||||
"ruleId": "942260"
|
||||
},
|
||||
{
|
||||
"ruleId": "942270"
|
||||
},
|
||||
{
|
||||
"ruleId": "942280"
|
||||
},
|
||||
{
|
||||
"ruleId": "942290"
|
||||
},
|
||||
{
|
||||
"ruleId": "942300"
|
||||
},
|
||||
{
|
||||
"ruleId": "942310"
|
||||
},
|
||||
{
|
||||
"ruleId": "942320"
|
||||
},
|
||||
{
|
||||
"ruleId": "942330"
|
||||
},
|
||||
{
|
||||
"ruleId": "942340"
|
||||
},
|
||||
{
|
||||
"ruleId": "942350"
|
||||
},
|
||||
{
|
||||
"ruleId": "942360"
|
||||
},
|
||||
{
|
||||
"ruleId": "942361"
|
||||
},
|
||||
{
|
||||
"ruleId": "942370"
|
||||
},
|
||||
{
|
||||
"ruleId": "942380"
|
||||
},
|
||||
{
|
||||
"ruleId": "942390"
|
||||
},
|
||||
{
|
||||
"ruleId": "942400"
|
||||
},
|
||||
{
|
||||
"ruleId": "942410"
|
||||
},
|
||||
{
|
||||
"ruleId": "942420"
|
||||
},
|
||||
{
|
||||
"ruleId": "942421"
|
||||
},
|
||||
{
|
||||
"ruleId": "942430"
|
||||
},
|
||||
{
|
||||
"ruleId": "942431"
|
||||
},
|
||||
{
|
||||
"ruleId": "942432"
|
||||
},
|
||||
{
|
||||
"ruleId": "942450"
|
||||
},
|
||||
{
|
||||
"ruleId": "942460"
|
||||
},
|
||||
{
|
||||
"ruleId": "942470"
|
||||
},
|
||||
{
|
||||
"ruleId": "942480"
|
||||
},
|
||||
{
|
||||
"ruleId": "942490"
|
||||
},
|
||||
{
|
||||
"ruleId": "942500"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -813,7 +948,7 @@
|
||||
"direction": "Inbound",
|
||||
"priority": 903,
|
||||
"protocol": "TCP",
|
||||
"sourceAddressPrefix": "211.125.140.74",
|
||||
"sourceAddressPrefix": "211.125.140.76",
|
||||
"sourceAddressPrefixes": [],
|
||||
"sourcePortRange": "*",
|
||||
"sourcePortRanges": []
|
||||
@ -1353,7 +1488,7 @@
|
||||
"direction": "Inbound",
|
||||
"priority": 903,
|
||||
"protocol": "TCP",
|
||||
"sourceAddressPrefix": "211.125.140.74",
|
||||
"sourceAddressPrefix": "211.125.140.76",
|
||||
"sourceAddressPrefixes": [],
|
||||
"sourcePortRange": "*",
|
||||
"sourcePortRanges": []
|
||||
@ -10880,6 +11015,10 @@
|
||||
"properties": {}
|
||||
}
|
||||
],
|
||||
"sslPolicy": {
|
||||
"policyName": "AppGwSslPolicy20220101",
|
||||
"policyType": "Predefined"
|
||||
},
|
||||
"sslProfiles": [],
|
||||
"trustedClientCertificates": [],
|
||||
"trustedRootCertificates": [],
|
||||
|
||||
@ -2,6 +2,6 @@
|
||||
"$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
|
||||
"spaces": 2,
|
||||
"generator-cli": {
|
||||
"version": "6.6.0"
|
||||
"version": "7.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1 +1 @@
|
||||
6.6.0
|
||||
7.0.0
|
||||
File diff suppressed because it is too large
Load Diff
@ -144,7 +144,7 @@ export const toPathString = function (url: URL) {
|
||||
*/
|
||||
export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) {
|
||||
return <T = unknown, R = AxiosResponse<T>>(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
|
||||
const axiosRequestArgs = {...axiosArgs.options, url: (configuration?.basePath || basePath) + axiosArgs.url};
|
||||
const axiosRequestArgs = {...axiosArgs.options, url: (configuration?.basePath || axios.defaults.baseURL || basePath) + axiosArgs.url};
|
||||
return axios.request<T, R>(axiosRequestArgs);
|
||||
};
|
||||
}
|
||||
|
||||
@ -46,4 +46,9 @@ export const errorCodes = [
|
||||
"E010804", // ライセンス数不足エラー
|
||||
"E010805", // ライセンス有効期限切れエラー
|
||||
"E010806", // ライセンス割り当て不可エラー
|
||||
"E010807", // ライセンス割り当て解除不可エラー
|
||||
"E010808", // ライセンス注文キャンセル不可エラー
|
||||
"E010809", // ライセンス発行キャンセル不可エラー(ステータスが変えられている場合)
|
||||
"E010810", // ライセンス発行キャンセル不可エラー(発行から一定期間経過した場合)
|
||||
"E010811", // ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合)
|
||||
] as const;
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||
import { LicenseOrderHistoryState } from "./state";
|
||||
import { getLicenseOrderHistoriesAsync, issueLicenseAsync } from "./operations";
|
||||
import {
|
||||
getLicenseOrderHistoriesAsync,
|
||||
issueLicenseAsync,
|
||||
cancelOrderAsync,
|
||||
cancelIssueAsync,
|
||||
} from "./operations";
|
||||
import { LIMIT_ORDER_HISORY_NUM } from "./constants";
|
||||
|
||||
const initialState: LicenseOrderHistoryState = {
|
||||
@ -61,6 +66,24 @@ export const licenseOrderHistorySlice = createSlice({
|
||||
builder.addCase(issueLicenseAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(cancelOrderAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(cancelOrderAsync.fulfilled, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
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;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import type { RootState } from "app/store";
|
||||
import { getTranslationID } from "translation";
|
||||
import { openSnackbar } from "features/ui/uiSlice";
|
||||
import { AccountsApi } from "../../../api/api";
|
||||
import { AccountsApi, LicensesApi } from "../../../api/api";
|
||||
import { Configuration } from "../../../api/configuration";
|
||||
import { ErrorObject, createErrorObject } from "../../../common/errors";
|
||||
import { OrderHistoryView } from "./types";
|
||||
@ -141,3 +141,137 @@ export const issueLicenseAsync = createAsyncThunk<
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
export const cancelOrderAsync = createAsyncThunk<
|
||||
{
|
||||
/* Empty Object */
|
||||
},
|
||||
{
|
||||
// パラメータ
|
||||
poNumber: string;
|
||||
},
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("licenses/cancelOrderAsync", async (args, thunkApi) => {
|
||||
const { poNumber } = args;
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration, accessToken } = state.auth;
|
||||
const config = new Configuration(configuration);
|
||||
const licensesApi = new LicensesApi(config);
|
||||
|
||||
try {
|
||||
await licensesApi.cancelOrder(
|
||||
{
|
||||
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 === "E010808") {
|
||||
errorMessage = getTranslationID(
|
||||
"orderHistoriesPage.message.alreadyLicenseIssueOrCancel"
|
||||
);
|
||||
}
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: errorMessage,
|
||||
})
|
||||
);
|
||||
|
||||
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 });
|
||||
}
|
||||
});
|
||||
|
||||
@ -306,3 +306,67 @@ export const allocateLicenseAsync = createAsyncThunk<
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
export const deallocateLicenseAsync = createAsyncThunk<
|
||||
// 正常時の戻り値の型
|
||||
{
|
||||
/* Empty Object */
|
||||
},
|
||||
// 引数
|
||||
{
|
||||
userId: number;
|
||||
},
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("users/deallocateLicenseAsync", async (args, thunkApi) => {
|
||||
const { userId } = args;
|
||||
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration, accessToken } = state.auth;
|
||||
const config = new Configuration(configuration);
|
||||
const usersApi = new UsersApi(config);
|
||||
|
||||
try {
|
||||
await usersApi.deallocateLicense(
|
||||
{
|
||||
userId,
|
||||
},
|
||||
{
|
||||
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 === "E010807") {
|
||||
errorMessage = getTranslationID(
|
||||
"userListPage.message.alreadyLicenseDeallocatedError"
|
||||
);
|
||||
}
|
||||
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: errorMessage,
|
||||
})
|
||||
);
|
||||
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
listUsersAsync,
|
||||
updateUserAsync,
|
||||
getAllocatableLicensesAsync,
|
||||
deallocateLicenseAsync,
|
||||
} from "./operations";
|
||||
import { RoleType, UserView } from "./types";
|
||||
|
||||
@ -298,6 +299,15 @@ export const userSlice = createSlice({
|
||||
builder.addCase(getAllocatableLicensesAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(deallocateLicenseAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(deallocateLicenseAsync.fulfilled, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(deallocateLicenseAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -2,7 +2,14 @@ import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import type { RootState } from "app/store";
|
||||
import { openSnackbar } from "features/ui/uiSlice";
|
||||
import { getTranslationID } from "translation";
|
||||
import { AccountsApi, GetTypistGroupsResponse } from "../../../api/api";
|
||||
import {
|
||||
AccountsApi,
|
||||
GetTypistGroupsResponse,
|
||||
GetTypistsResponse,
|
||||
CreateTypistGroupRequest,
|
||||
Typist,
|
||||
TypistGroup,
|
||||
} from "../../../api/api";
|
||||
import { Configuration } from "../../../api/configuration";
|
||||
import { ErrorObject, createErrorObject } from "../../../common/errors";
|
||||
|
||||
@ -15,7 +22,7 @@ export const listTypistGroupsAsync = createAsyncThunk<
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("dictations/listTypistGroupsAsync", async (args, thunkApi) => {
|
||||
>("workflow/listTypistGroupsAsync", async (args, thunkApi) => {
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
@ -41,3 +48,205 @@ export const listTypistGroupsAsync = createAsyncThunk<
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
export const listTypistsAsync = createAsyncThunk<
|
||||
GetTypistsResponse,
|
||||
void,
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("workflow/listTypistsAsync", 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);
|
||||
|
||||
try {
|
||||
const typists = await accountsApi.getTypists({
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
|
||||
return typists.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 createTypistGroupAsync = createAsyncThunk<
|
||||
{
|
||||
/* Empty Object */
|
||||
},
|
||||
CreateTypistGroupRequest,
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("workflow/createTypistGroupAsync", 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);
|
||||
|
||||
try {
|
||||
await accountsApi.createTypistGroup(args, {
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "info",
|
||||
message: getTranslationID("common.message.success"),
|
||||
})
|
||||
);
|
||||
return {};
|
||||
} catch (e) {
|
||||
// e ⇒ errorObjectに変換"
|
||||
const error = createErrorObject(e);
|
||||
|
||||
const message =
|
||||
error.statusCode === 400
|
||||
? getTranslationID("typistGroupSetting.message.groupSaveFailedError")
|
||||
: getTranslationID("common.message.internalServerError");
|
||||
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message,
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
export const getTypistGroupAsync = createAsyncThunk<
|
||||
{
|
||||
typists: Typist[];
|
||||
typistGroup: TypistGroup;
|
||||
selectedTypistIds: number[];
|
||||
},
|
||||
{ typistGroupId: number },
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("workflow/getTypistGroupAsync", async (args, thunkApi) => {
|
||||
const { typistGroupId } = 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 {
|
||||
// タイピスト取得処理が別にあるが、storeの状態を意識せずに処理を行うためにここで取得する
|
||||
const { typists } = (
|
||||
await accountsApi.getTypists({
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
})
|
||||
).data;
|
||||
const { typistGroupName, typistIds } = (
|
||||
await accountsApi.getTypistGroup(typistGroupId, {
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
})
|
||||
).data;
|
||||
return {
|
||||
typists,
|
||||
typistGroup: { id: typistGroupId, name: typistGroupName },
|
||||
selectedTypistIds: typistIds,
|
||||
};
|
||||
} catch (e) {
|
||||
// e ⇒ errorObjectに変換"
|
||||
const error = createErrorObject(e);
|
||||
|
||||
const message = getTranslationID("common.message.internalServerError");
|
||||
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message,
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
export const updateTypistGroupAsync = createAsyncThunk<
|
||||
{
|
||||
/* Empty Object */
|
||||
},
|
||||
void,
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("workflow/updateTypistGroupAsync", async (args, thunkApi) => {
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration, accessToken } = state.auth;
|
||||
const { updateTypistGroupId, selectedTypists, groupName } =
|
||||
state.typistGroup.apps;
|
||||
const config = new Configuration(configuration);
|
||||
const accountsApi = new AccountsApi(config);
|
||||
if (!updateTypistGroupId) {
|
||||
throw new Error("updateTypistGroupId is undefined.");
|
||||
}
|
||||
|
||||
try {
|
||||
await accountsApi.updateTypistGroup(
|
||||
updateTypistGroupId,
|
||||
{
|
||||
typistGroupName: groupName,
|
||||
typistIds: selectedTypists.map((x) => x.id),
|
||||
},
|
||||
{
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
}
|
||||
);
|
||||
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "info",
|
||||
message: getTranslationID("common.message.success"),
|
||||
})
|
||||
);
|
||||
return {};
|
||||
} catch (e) {
|
||||
// e ⇒ errorObjectに変換"
|
||||
const error = createErrorObject(e);
|
||||
|
||||
const message =
|
||||
error.statusCode === 400
|
||||
? getTranslationID("typistGroupSetting.message.groupSaveFailedError")
|
||||
: getTranslationID("common.message.internalServerError");
|
||||
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message,
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
@ -5,3 +5,25 @@ export const selectTypistGroups = (state: RootState) =>
|
||||
|
||||
export const selectIsLoading = (state: RootState) =>
|
||||
state.typistGroup.apps.isLoading;
|
||||
|
||||
export const selectTypists = (state: RootState) =>
|
||||
state.typistGroup.domain.typists;
|
||||
|
||||
export const selectPoolTypists = (state: RootState) =>
|
||||
state.typistGroup.domain.typists.filter(
|
||||
(t) => !state.typistGroup.apps.selectedTypists.some((x) => t.id === x.id)
|
||||
);
|
||||
|
||||
export const selectSelectedTypists = (state: RootState) =>
|
||||
state.typistGroup.apps.selectedTypists;
|
||||
|
||||
export const selectGroupName = (state: RootState) =>
|
||||
state.typistGroup.apps.groupName;
|
||||
|
||||
export const selectAddGroupErrors = (state: RootState) => {
|
||||
const hasErrorEmptyGroupName = state.typistGroup.apps.groupName === "";
|
||||
const hasErrorSelectedTypistsEmpty =
|
||||
state.typistGroup.apps.selectedTypists.length === 0;
|
||||
|
||||
return { hasErrorEmptyGroupName, hasErrorSelectedTypistsEmpty };
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { TypistGroup } from "../../../api/api";
|
||||
import { Typist, TypistGroup } from "../../../api/api";
|
||||
|
||||
export interface TypistGroupState {
|
||||
apps: Apps;
|
||||
@ -7,8 +7,12 @@ export interface TypistGroupState {
|
||||
|
||||
export interface Apps {
|
||||
isLoading: boolean;
|
||||
selectedTypists: Typist[];
|
||||
groupName: string;
|
||||
updateTypistGroupId?: number;
|
||||
}
|
||||
|
||||
export interface Domain {
|
||||
typistGroups: TypistGroup[];
|
||||
typists: Typist[];
|
||||
}
|
||||
|
||||
@ -1,20 +1,61 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||
import { Typist } from "api";
|
||||
import { TypistGroupState } from "./state";
|
||||
import { listTypistGroupsAsync } from "./operations";
|
||||
import {
|
||||
getTypistGroupAsync,
|
||||
listTypistGroupsAsync,
|
||||
listTypistsAsync,
|
||||
updateTypistGroupAsync,
|
||||
} from "./operations";
|
||||
|
||||
const initialState: TypistGroupState = {
|
||||
apps: {
|
||||
isLoading: false,
|
||||
selectedTypists: [],
|
||||
groupName: "",
|
||||
},
|
||||
domain: {
|
||||
typistGroups: [],
|
||||
typists: [],
|
||||
},
|
||||
};
|
||||
|
||||
export const typistGroupSlice = createSlice({
|
||||
name: "typistGroup",
|
||||
initialState,
|
||||
reducers: {},
|
||||
reducers: {
|
||||
cleanupTypistGroup: (state) => {
|
||||
state.apps.groupName = "";
|
||||
state.apps.selectedTypists = [];
|
||||
state.apps.updateTypistGroupId = undefined;
|
||||
state.domain.typists = [];
|
||||
},
|
||||
addSelectedTypist: (state, action: PayloadAction<{ typist: Typist }>) => {
|
||||
const { typist } = action.payload;
|
||||
|
||||
const selectedTypists = [...state.apps.selectedTypists, typist];
|
||||
if (!state.apps.selectedTypists.find((x) => x.id === typist.id)) {
|
||||
state.apps.selectedTypists = selectedTypists.sort(
|
||||
(a, b) => a.id - b.id
|
||||
);
|
||||
}
|
||||
},
|
||||
removeSelectedTypist: (
|
||||
state,
|
||||
action: PayloadAction<{ typist: Typist }>
|
||||
) => {
|
||||
const { typist } = action.payload;
|
||||
|
||||
const selectedTypists = state.apps.selectedTypists.filter(
|
||||
(x) => x.id !== typist.id
|
||||
);
|
||||
state.apps.selectedTypists = selectedTypists.sort((a, b) => a.id - b.id);
|
||||
},
|
||||
changeGroupName: (state, action: PayloadAction<{ groupName: string }>) => {
|
||||
const { groupName } = action.payload;
|
||||
state.apps.groupName = groupName;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(listTypistGroupsAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
@ -26,7 +67,53 @@ export const typistGroupSlice = createSlice({
|
||||
builder.addCase(listTypistGroupsAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(listTypistsAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(listTypistsAsync.fulfilled, (state, action) => {
|
||||
state.domain.typists = action.payload.typists;
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(listTypistsAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(getTypistGroupAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(getTypistGroupAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(getTypistGroupAsync.fulfilled, (state, action) => {
|
||||
const { typistGroup, selectedTypistIds, typists } = action.payload;
|
||||
// 対象タイピストグループのID・名前を設定
|
||||
state.apps.updateTypistGroupId = typistGroup.id;
|
||||
state.apps.groupName = typistGroup.name;
|
||||
|
||||
// 選択済みのタイピストを設定
|
||||
state.apps.selectedTypists = typists
|
||||
.filter((x) => selectedTypistIds.includes(x.id))
|
||||
.sort((a, b) => a.id - b.id);
|
||||
// すべてのタイピストを設定
|
||||
state.domain.typists = typists;
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(updateTypistGroupAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(updateTypistGroupAsync.fulfilled, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(updateTypistGroupAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
cleanupTypistGroup,
|
||||
addSelectedTypist,
|
||||
removeSelectedTypist,
|
||||
changeGroupName,
|
||||
} = typistGroupSlice.actions;
|
||||
|
||||
export default typistGroupSlice.reducer;
|
||||
|
||||
@ -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";
|
||||
@ -18,9 +20,11 @@ import {
|
||||
selectTotal,
|
||||
selectTotalPage,
|
||||
issueLicenseAsync,
|
||||
cancelOrderAsync,
|
||||
selectOffset,
|
||||
savePageInfo,
|
||||
selectCompanyName,
|
||||
cancelIssueAsync,
|
||||
} from "features/license/licenseOrderHistory";
|
||||
import { selectSelectedRow } from "features/license/partnerLicense";
|
||||
import undo from "../../assets/images/undo.svg";
|
||||
@ -85,6 +89,49 @@ export const LicenseOrderHistory: React.FC<LicenseOrderHistoryProps> = (
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
// Order Cancelボタンを押下時の処理
|
||||
const onCancelOrder = useCallback(
|
||||
async (poNumber: string) => {
|
||||
// ダイアログ確認
|
||||
// eslint-disable-next-line no-alert
|
||||
if (window.confirm(t(getTranslationID("common.message.dialogConfirm")))) {
|
||||
// ライセンス注文キャンセルAPIの呼び出し
|
||||
const { meta } = await dispatch(
|
||||
cancelOrderAsync({
|
||||
poNumber,
|
||||
})
|
||||
);
|
||||
if (meta.requestStatus === "fulfilled") {
|
||||
UpdateHistoriesList();
|
||||
}
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[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(
|
||||
@ -207,12 +254,16 @@ export const LicenseOrderHistory: React.FC<LicenseOrderHistoryProps> = (
|
||||
className={`${styles.menuAction} ${styles.inTable}`}
|
||||
>
|
||||
<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.ISSUE_REQESTING
|
||||
? styles.isActive
|
||||
: ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
onCancelOrder(x.poNumber);
|
||||
}}
|
||||
>
|
||||
{t(
|
||||
getTranslationID(
|
||||
@ -231,7 +282,12 @@ export const LicenseOrderHistory: React.FC<LicenseOrderHistoryProps> = (
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||
<a
|
||||
className={`${styles.colorLink} ${
|
||||
x.status === STATUS.ISSUE_REQESTING
|
||||
x.status === STATUS.ISSUE_REQESTING &&
|
||||
/* ログインユーザーの子階層のみissueボタンを表示 */
|
||||
selectedRow.tier > 1 &&
|
||||
isApproveTier([
|
||||
(selectedRow.tier - 1).toString(),
|
||||
])
|
||||
? styles.isActive
|
||||
: ""
|
||||
}`}
|
||||
@ -248,12 +304,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(
|
||||
@ -345,4 +410,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;
|
||||
|
||||
@ -0,0 +1,203 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import styles from "styles/app.module.scss";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { Typist } from "api";
|
||||
import { AppDispatch } from "app/store";
|
||||
import { getTranslationID } from "translation";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
addSelectedTypist,
|
||||
listTypistsAsync,
|
||||
removeSelectedTypist,
|
||||
selectPoolTypists,
|
||||
selectSelectedTypists,
|
||||
selectGroupName,
|
||||
changeGroupName,
|
||||
selectAddGroupErrors,
|
||||
cleanupTypistGroup,
|
||||
} from "features/workflow/typistGroup";
|
||||
import {
|
||||
createTypistGroupAsync,
|
||||
listTypistGroupsAsync,
|
||||
} from "features/workflow/typistGroup/operations";
|
||||
import { openSnackbar } from "features/ui";
|
||||
import close from "../../assets/images/close.svg";
|
||||
|
||||
interface AddTypistGroupPopupProps {
|
||||
onClose: (isChanged: boolean) => void;
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
export const AddTypistGroupPopup: React.FC<AddTypistGroupPopupProps> = (
|
||||
props
|
||||
) => {
|
||||
const { onClose, isOpen } = props;
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
const [t] = useTranslation();
|
||||
|
||||
const [isPushAddButton, setIsPushAddButton] = useState<boolean>(false);
|
||||
|
||||
const poolTypists = useSelector(selectPoolTypists);
|
||||
const selectedTypists = useSelector(selectSelectedTypists);
|
||||
const groupName = useSelector(selectGroupName);
|
||||
|
||||
const { hasErrorEmptyGroupName, hasErrorSelectedTypistsEmpty } =
|
||||
useSelector(selectAddGroupErrors);
|
||||
|
||||
// 開閉時のみ実行
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
dispatch(listTypistsAsync());
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isOpen]);
|
||||
|
||||
// ポップアップを閉じる処理
|
||||
const closePopup = useCallback(() => {
|
||||
setIsPushAddButton(false);
|
||||
onClose(false);
|
||||
dispatch(cleanupTypistGroup());
|
||||
}, [dispatch, onClose]);
|
||||
|
||||
// グループ追加を実行
|
||||
const addTypistGroup = useCallback(async () => {
|
||||
setIsPushAddButton(true);
|
||||
// 入力チェック
|
||||
if (hasErrorEmptyGroupName || hasErrorSelectedTypistsEmpty) {
|
||||
if (hasErrorSelectedTypistsEmpty) {
|
||||
dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: t(
|
||||
getTranslationID(
|
||||
"typistGroupSetting.message.selectedTypistEmptyError"
|
||||
)
|
||||
),
|
||||
})
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// グループ追加APIを実行
|
||||
const { meta } = await dispatch(
|
||||
createTypistGroupAsync({
|
||||
typistGroupName: groupName,
|
||||
typistIds: selectedTypists.map((typist) => typist.id),
|
||||
})
|
||||
);
|
||||
setIsPushAddButton(false);
|
||||
if (meta.requestStatus === "fulfilled") {
|
||||
closePopup();
|
||||
dispatch(listTypistGroupsAsync());
|
||||
}
|
||||
}, [
|
||||
t,
|
||||
closePopup,
|
||||
dispatch,
|
||||
groupName,
|
||||
selectedTypists,
|
||||
hasErrorEmptyGroupName,
|
||||
hasErrorSelectedTypistsEmpty,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className={`${styles.modal} ${isOpen ? styles.isShow : ""}`}>
|
||||
<div className={styles.modalBox}>
|
||||
<p className={styles.modalTitle}>
|
||||
{t(getTranslationID("typistGroupSetting.label.addTypistGroup"))}
|
||||
<button type="button" onClick={closePopup}>
|
||||
<img src={close} className={styles.modalTitleIcon} alt="close" />
|
||||
</button>
|
||||
</p>
|
||||
<form className={styles.form}>
|
||||
<dl className={`${styles.formList} ${styles.hasbg}`}>
|
||||
<dt className={styles.formTitle} />
|
||||
<dt>{t(getTranslationID("typistGroupSetting.label.groupName"))}</dt>
|
||||
<dd>
|
||||
<input
|
||||
type="text"
|
||||
size={40}
|
||||
maxLength={50}
|
||||
className={styles.formInput}
|
||||
value={groupName}
|
||||
onChange={(e) =>
|
||||
dispatch(changeGroupName({ groupName: e.target.value }))
|
||||
}
|
||||
/>
|
||||
{isPushAddButton && hasErrorEmptyGroupName && (
|
||||
<span className={styles.formError}>
|
||||
{t(getTranslationID("common.message.inputEmptyError"))}
|
||||
</span>
|
||||
)}
|
||||
</dd>
|
||||
<dt className={styles.formTitle}>
|
||||
{t(getTranslationID("typistGroupSetting.label.transcriptionist"))}
|
||||
</dt>
|
||||
<dd className={`${styles.formChange} ${styles.last}`}>
|
||||
<ul className={styles.chooseMember}>
|
||||
<li className={styles.changeTitle}>
|
||||
{t(getTranslationID("typistGroupSetting.label.selected"))}
|
||||
</li>
|
||||
{selectedTypists.map((typist: Typist) => (
|
||||
<li key={typist.id}>
|
||||
<input
|
||||
type="checkbox"
|
||||
className={styles.formCheck}
|
||||
value={typist.name}
|
||||
id={`${typist.id}`}
|
||||
checked
|
||||
onClick={() => dispatch(removeSelectedTypist({ typist }))}
|
||||
/>
|
||||
<label
|
||||
htmlFor={`${typist.id}`}
|
||||
title={t(
|
||||
getTranslationID("typistGroupSetting.label.remove")
|
||||
)}
|
||||
>
|
||||
{typist.name}
|
||||
</label>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p />
|
||||
<ul className={styles.holdMember}>
|
||||
<li className={styles.changeTitle}>
|
||||
{t(getTranslationID("typistGroupSetting.label.pool"))}
|
||||
</li>
|
||||
{poolTypists.map((typist: Typist) => (
|
||||
<li key={typist.id}>
|
||||
<input
|
||||
type="checkbox"
|
||||
className={styles.formCheck}
|
||||
value={typist.name}
|
||||
id={`${typist.id}`}
|
||||
onClick={() => dispatch(addSelectedTypist({ typist }))}
|
||||
/>
|
||||
<label
|
||||
htmlFor={`${typist.id}`}
|
||||
title={t(
|
||||
getTranslationID("typistGroupSetting.label.add")
|
||||
)}
|
||||
>
|
||||
{typist.name}
|
||||
</label>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</dd>
|
||||
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||
<input
|
||||
type="button"
|
||||
name="submit"
|
||||
value={t(getTranslationID("typistGroupSetting.label.save"))}
|
||||
className={`${styles.formSubmit} ${styles.marginBtm1} ${styles.isActive}`}
|
||||
onClick={addTypistGroup}
|
||||
/>
|
||||
</dd>
|
||||
</dl>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,197 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import styles from "styles/app.module.scss";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { Typist } from "api";
|
||||
import { AppDispatch } from "app/store";
|
||||
import { getTranslationID } from "translation";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
addSelectedTypist,
|
||||
cleanupTypistGroup,
|
||||
removeSelectedTypist,
|
||||
selectPoolTypists,
|
||||
selectSelectedTypists,
|
||||
selectGroupName,
|
||||
changeGroupName,
|
||||
selectAddGroupErrors,
|
||||
} from "features/workflow/typistGroup";
|
||||
import {
|
||||
getTypistGroupAsync,
|
||||
listTypistGroupsAsync,
|
||||
updateTypistGroupAsync,
|
||||
} from "features/workflow/typistGroup/operations";
|
||||
import { openSnackbar } from "features/ui";
|
||||
import close from "../../assets/images/close.svg";
|
||||
|
||||
interface EditTypistGroupPopupProps {
|
||||
onClose: (isChanged: boolean) => void;
|
||||
isOpen: boolean;
|
||||
typistGroupId: number;
|
||||
}
|
||||
|
||||
export const EditTypistGroupPopup: React.FC<EditTypistGroupPopupProps> = (
|
||||
props
|
||||
) => {
|
||||
const { onClose, isOpen, typistGroupId } = props;
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
const [t] = useTranslation();
|
||||
|
||||
const [isPushEditButton, setIsPushEditButton] = useState<boolean>(false);
|
||||
|
||||
const poolTypists = useSelector(selectPoolTypists);
|
||||
const selectedTypists = useSelector(selectSelectedTypists);
|
||||
const groupName = useSelector(selectGroupName);
|
||||
|
||||
const { hasErrorEmptyGroupName, hasErrorSelectedTypistsEmpty } =
|
||||
useSelector(selectAddGroupErrors);
|
||||
|
||||
// 表示時のみ実行
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
dispatch(getTypistGroupAsync({ typistGroupId }));
|
||||
}
|
||||
}, [dispatch, isOpen, typistGroupId]);
|
||||
|
||||
// ポップアップを閉じる処理
|
||||
const closePopup = useCallback(() => {
|
||||
setIsPushEditButton(false);
|
||||
onClose(false);
|
||||
dispatch(cleanupTypistGroup());
|
||||
}, [dispatch, onClose]);
|
||||
|
||||
// グループ更新を実行
|
||||
const editTypistGroup = useCallback(async () => {
|
||||
setIsPushEditButton(true);
|
||||
// 入力チェック
|
||||
if (hasErrorEmptyGroupName || hasErrorSelectedTypistsEmpty) {
|
||||
if (hasErrorSelectedTypistsEmpty) {
|
||||
dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: t(
|
||||
getTranslationID(
|
||||
"typistGroupSetting.message.selectedTypistEmptyError"
|
||||
)
|
||||
),
|
||||
})
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// グループ追加APIを実行
|
||||
const { meta } = await dispatch(updateTypistGroupAsync());
|
||||
setIsPushEditButton(false);
|
||||
if (meta.requestStatus === "fulfilled") {
|
||||
closePopup();
|
||||
dispatch(listTypistGroupsAsync());
|
||||
dispatch(cleanupTypistGroup());
|
||||
}
|
||||
}, [
|
||||
t,
|
||||
closePopup,
|
||||
dispatch,
|
||||
hasErrorEmptyGroupName,
|
||||
hasErrorSelectedTypistsEmpty,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className={`${styles.modal} ${isOpen ? styles.isShow : ""}`}>
|
||||
<div className={styles.modalBox}>
|
||||
<p className={styles.modalTitle}>
|
||||
{t(getTranslationID("typistGroupSetting.label.editTypistGroup"))}
|
||||
<button type="button" onClick={closePopup}>
|
||||
<img src={close} className={styles.modalTitleIcon} alt="close" />
|
||||
</button>
|
||||
</p>
|
||||
<form className={styles.form}>
|
||||
<dl className={`${styles.formList} ${styles.hasbg}`}>
|
||||
<dt className={styles.formTitle} />
|
||||
<dt>{t(getTranslationID("typistGroupSetting.label.groupName"))}</dt>
|
||||
<dd>
|
||||
<input
|
||||
type="text"
|
||||
size={40}
|
||||
maxLength={50}
|
||||
className={styles.formInput}
|
||||
value={groupName}
|
||||
onChange={(e) =>
|
||||
dispatch(changeGroupName({ groupName: e.target.value }))
|
||||
}
|
||||
/>
|
||||
{isPushEditButton && hasErrorEmptyGroupName && (
|
||||
<span className={styles.formError}>
|
||||
{t(getTranslationID("common.message.inputEmptyError"))}
|
||||
</span>
|
||||
)}
|
||||
</dd>
|
||||
<dt className={styles.formTitle}>
|
||||
{t(getTranslationID("typistGroupSetting.label.transcriptionist"))}
|
||||
</dt>
|
||||
<dd className={`${styles.formChange} ${styles.last}`}>
|
||||
<ul className={styles.chooseMember}>
|
||||
<li className={styles.changeTitle}>
|
||||
{t(getTranslationID("typistGroupSetting.label.selected"))}
|
||||
</li>
|
||||
{selectedTypists.map((typist: Typist) => (
|
||||
<li key={typist.id}>
|
||||
<input
|
||||
type="checkbox"
|
||||
className={styles.formCheck}
|
||||
value={typist.name}
|
||||
id={`${typist.id}`}
|
||||
checked
|
||||
onClick={() => dispatch(removeSelectedTypist({ typist }))}
|
||||
/>
|
||||
<label
|
||||
htmlFor={`${typist.id}`}
|
||||
title={t(
|
||||
getTranslationID("typistGroupSetting.label.remove")
|
||||
)}
|
||||
>
|
||||
{typist.name}
|
||||
</label>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p />
|
||||
<ul className={styles.holdMember}>
|
||||
<li className={styles.changeTitle}>
|
||||
{t(getTranslationID("typistGroupSetting.label.pool"))}
|
||||
</li>
|
||||
{poolTypists.map((typist: Typist) => (
|
||||
<li key={typist.id}>
|
||||
<input
|
||||
type="checkbox"
|
||||
className={styles.formCheck}
|
||||
value={typist.name}
|
||||
id={`${typist.id}`}
|
||||
onClick={() => dispatch(addSelectedTypist({ typist }))}
|
||||
/>
|
||||
<label
|
||||
htmlFor={`${typist.id}`}
|
||||
title={t(
|
||||
getTranslationID("typistGroupSetting.label.add")
|
||||
)}
|
||||
>
|
||||
{typist.name}
|
||||
</label>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</dd>
|
||||
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||
<input
|
||||
type="button"
|
||||
name="submit"
|
||||
value={t(getTranslationID("typistGroupSetting.label.save"))}
|
||||
className={`${styles.formSubmit} ${styles.marginBtm1} ${styles.isActive}`}
|
||||
onClick={editTypistGroup}
|
||||
/>
|
||||
</dd>
|
||||
</dl>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from "react";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import Header from "components/header";
|
||||
import Footer from "components/footer";
|
||||
import styles from "styles/app.module.scss";
|
||||
@ -15,6 +15,8 @@ import {
|
||||
import { AppDispatch } from "app/store";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getTranslationID } from "translation";
|
||||
import { AddTypistGroupPopup } from "./addTypistGroupPopup";
|
||||
import { EditTypistGroupPopup } from "./editTypistGroupPopup";
|
||||
|
||||
const TypistGroupSettingPage: React.FC = (): JSX.Element => {
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
@ -23,95 +25,135 @@ const TypistGroupSettingPage: React.FC = (): JSX.Element => {
|
||||
const isLoading = useSelector(selectIsLoading);
|
||||
const typistGroup = useSelector(selectTypistGroups);
|
||||
|
||||
const [isAddPopupOpen, setIsAddPopupOpen] = useState(false);
|
||||
const [isEditPopupOpen, setIsEditPopupOpen] = useState(false);
|
||||
const [editTypistGroupId, setEditTypistGroupId] = useState<number>(NaN);
|
||||
const onAddPopupOpen = useCallback(() => {
|
||||
// typist一覧を取得
|
||||
setIsAddPopupOpen(true);
|
||||
}, [setIsAddPopupOpen]);
|
||||
|
||||
const onEditPopupOpen = useCallback(
|
||||
(typistGroupId: number) => {
|
||||
setEditTypistGroupId(typistGroupId);
|
||||
setIsEditPopupOpen(true);
|
||||
},
|
||||
[setIsEditPopupOpen]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(listTypistGroupsAsync());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<Header userName="XXXXXX" />
|
||||
<UpdateTokenTimer />
|
||||
<main className={styles.main}>
|
||||
<div>
|
||||
<div className={styles.pageHeader}>
|
||||
<h1 className={styles.pageTitle}>
|
||||
{t(getTranslationID("workflowPage.label.title"))}
|
||||
</h1>
|
||||
<p className={styles.pageTx}>{` ${t(
|
||||
getTranslationID("typistGroupSetting.label.title")
|
||||
)}`}</p>
|
||||
</div>
|
||||
|
||||
<section className={styles.workflow}>
|
||||
<div>
|
||||
<ul className={styles.menuAction}>
|
||||
<li>
|
||||
<a
|
||||
href="/workflow"
|
||||
className={`${styles.menuLink} ${styles.isActive}`}
|
||||
>
|
||||
<img src={undo} alt="" className={styles.menuIcon} />
|
||||
{t(getTranslationID("typistGroupSetting.label.return"))}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a className={`${styles.menuLink} ${styles.isActive}`}>
|
||||
<img src={group_add} alt="" className={styles.menuIcon} />
|
||||
|
||||
{t(getTranslationID("typistGroupSetting.label.addGroup"))}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<table className={`${styles.table} ${styles.group}`}>
|
||||
<tr className={styles.tableHeader}>
|
||||
<th className={styles.noLine}>
|
||||
{t(getTranslationID("typistGroupSetting.label.groupName"))}
|
||||
</th>
|
||||
<th>{/** empty th */}</th>
|
||||
</tr>
|
||||
{!isLoading && typistGroup.length === 0 ? (
|
||||
<p style={{ margin: "10px", textAlign: "center" }}>
|
||||
{t(getTranslationID("common.message.listEmpty"))}
|
||||
</p>
|
||||
) : (
|
||||
typistGroup.map((group) => (
|
||||
<tr key={group.id}>
|
||||
<td>{group.name}</td>
|
||||
<td>
|
||||
<ul
|
||||
className={`${styles.menuAction} ${styles.inTable}`}
|
||||
>
|
||||
<li>
|
||||
<a
|
||||
className={`${styles.menuLink} ${styles.isActive}`}
|
||||
>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"typistGroupSetting.label.edit"
|
||||
)
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</table>
|
||||
{isLoading && (
|
||||
<img
|
||||
src={progress_activit}
|
||||
className={styles.icLoading}
|
||||
alt="Loading"
|
||||
/>
|
||||
)}
|
||||
<>
|
||||
<AddTypistGroupPopup
|
||||
onClose={() => {
|
||||
setIsAddPopupOpen(false);
|
||||
}}
|
||||
isOpen={isAddPopupOpen}
|
||||
/>
|
||||
<EditTypistGroupPopup
|
||||
onClose={() => {
|
||||
setIsEditPopupOpen(false);
|
||||
}}
|
||||
isOpen={isEditPopupOpen}
|
||||
typistGroupId={editTypistGroupId}
|
||||
/>
|
||||
<div className={styles.wrap}>
|
||||
<Header userName="XXXXXX" />
|
||||
<UpdateTokenTimer />
|
||||
<main className={styles.main}>
|
||||
<div>
|
||||
<div className={styles.pageHeader}>
|
||||
<h1 className={styles.pageTitle}>
|
||||
{t(getTranslationID("workflowPage.label.title"))}
|
||||
</h1>
|
||||
<p className={styles.pageTx}>{` ${t(
|
||||
getTranslationID("typistGroupSetting.label.title")
|
||||
)}`}</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
<section className={styles.workflow}>
|
||||
<div>
|
||||
<ul className={styles.menuAction}>
|
||||
<li>
|
||||
<a
|
||||
href="/workflow"
|
||||
className={`${styles.menuLink} ${styles.isActive}`}
|
||||
>
|
||||
<img src={undo} alt="" className={styles.menuIcon} />
|
||||
{t(getTranslationID("typistGroupSetting.label.return"))}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
|
||||
<a
|
||||
className={`${styles.menuLink} ${styles.isActive}`}
|
||||
onClick={onAddPopupOpen}
|
||||
>
|
||||
<img src={group_add} alt="" className={styles.menuIcon} />
|
||||
{t(getTranslationID("typistGroupSetting.label.addGroup"))}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<table className={`${styles.table} ${styles.group}`}>
|
||||
<tr className={styles.tableHeader}>
|
||||
<th className={styles.noLine}>
|
||||
{t(
|
||||
getTranslationID("typistGroupSetting.label.groupName")
|
||||
)}
|
||||
</th>
|
||||
<th>{/** empty th */}</th>
|
||||
</tr>
|
||||
{!isLoading && typistGroup.length === 0 ? (
|
||||
<p style={{ margin: "10px", textAlign: "center" }}>
|
||||
{t(getTranslationID("common.message.listEmpty"))}
|
||||
</p>
|
||||
) : (
|
||||
typistGroup.map((group) => (
|
||||
<tr key={group.id}>
|
||||
<td>{group.name}</td>
|
||||
<td>
|
||||
<ul
|
||||
className={`${styles.menuAction} ${styles.inTable}`}
|
||||
>
|
||||
<li>
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
|
||||
<a
|
||||
className={`${styles.menuLink} ${styles.isActive}`}
|
||||
onClick={() => {
|
||||
onEditPopupOpen(group.id);
|
||||
}}
|
||||
>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"typistGroupSetting.label.edit"
|
||||
)
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</table>
|
||||
{isLoading && (
|
||||
<img
|
||||
src={progress_activit}
|
||||
className={styles.icLoading}
|
||||
alt="Loading"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
listUsersAsync,
|
||||
selectUserViews,
|
||||
selectIsLoading,
|
||||
deallocateLicenseAsync,
|
||||
} from "features/user";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getTranslationID } from "translation";
|
||||
@ -57,6 +58,24 @@ const UserListPage: React.FC = (): JSX.Element => {
|
||||
[setIsAllocateLicensePopupOpen, dispatch]
|
||||
);
|
||||
|
||||
const onLicenseDeallocation = useCallback(
|
||||
async (userId: number) => {
|
||||
// ダイアログ確認
|
||||
if (
|
||||
/* eslint-disable-next-line no-alert */
|
||||
!window.confirm(t(getTranslationID("common.message.dialogConfirm")))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { meta } = await dispatch(deallocateLicenseAsync({ userId }));
|
||||
if (meta.requestStatus === "fulfilled") {
|
||||
dispatch(listUsersAsync());
|
||||
}
|
||||
},
|
||||
[dispatch, t]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// ユーザ一覧取得処理を呼び出す
|
||||
dispatch(listUsersAsync());
|
||||
@ -196,7 +215,18 @@ const UserListPage: React.FC = (): JSX.Element => {
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="">
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
|
||||
<a
|
||||
className={
|
||||
user.licenseStatus ===
|
||||
LICENSE_STATUS.NOLICENSE
|
||||
? styles.isDisable
|
||||
: ""
|
||||
}
|
||||
onClick={() => {
|
||||
onLicenseDeallocation(user.id);
|
||||
}}
|
||||
>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"userListPage.label.licenseDeallocation"
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
{
|
||||
"common": {
|
||||
"message": {
|
||||
"inputEmptyError": "(de)Error Message",
|
||||
"inputEmptyError": "(de)この項目の入力は必須です。入力してください。",
|
||||
"passwordIncorrectError": "(de)Error Message",
|
||||
"emailIncorrectError": "(de)Error Message",
|
||||
"internalServerError": "(de)処理に失敗しました。時間をおいて再実行しても解決しない場合はシステム管理者にお問い合わせください。",
|
||||
"listEmpty": "(de)検索結果が0件です。",
|
||||
"dialogConfirm": "(de)操作を実行しますか?",
|
||||
"success": "(de)処理に成功しました。"
|
||||
"success": "(de)処理に成功しました。",
|
||||
"permissionDeniedError": "(de)操作を実行する権限がありません。"
|
||||
},
|
||||
"label": {
|
||||
"cancel": "(de)Cancel",
|
||||
@ -113,7 +114,8 @@
|
||||
"authorIdConflictError": "(de)このAuthor IDは既に登録されています。他のAuthor IDで登録してください。",
|
||||
"authorIdIncorrectError": "(de)Author IDの形式が不正です。Author IDは半角英数字(大文字)と\"_\"のみ入力可能です。",
|
||||
"roleChangeError": "(de)Roleの変更に失敗しました。画面を更新して再度ユーザー情報を取得してください。",
|
||||
"encryptionPasswordCorrectError": "(de)EncryptionPasswordがルールを満たしていません。"
|
||||
"encryptionPasswordCorrectError": "(de)EncryptionPasswordがルールを満たしていません。",
|
||||
"alreadyLicenseDeallocatedError": "(de)すでにライセンス割り当てが解除されています。画面を更新して再度ご確認下さい。"
|
||||
},
|
||||
"label": {
|
||||
"title": "(de)User",
|
||||
@ -235,7 +237,7 @@
|
||||
"deleteDictation": "(de)Delete Dictation",
|
||||
"selectedTranscriptionist": "(de)Selected",
|
||||
"poolTranscriptionist": "(de)Pool",
|
||||
"saveChanges": "(de)Save changes"
|
||||
"saveChanges": "(de)Save"
|
||||
}
|
||||
},
|
||||
"cardLicenseIssuePopupPage": {
|
||||
@ -328,7 +330,11 @@
|
||||
},
|
||||
"message": {
|
||||
"notEnoughOfNumberOfLicense": "(de)ライセンスが不足しているため、発行することができませんでした。ライセンスの注文を行ってください。",
|
||||
"alreadyIssueLicense": "(de)すでに発行済みの注文です。画面を更新してください。"
|
||||
"alreadyIssueLicense": "(de)すでに発行済みの注文です。画面を更新してください。",
|
||||
"alreadyLicenseIssueOrCancel": "(de)ライセンス注文のキャンセルに失敗しました。選択された注文はすでに発行またはキャンセルされています。画面を更新して再度ご確認ください。",
|
||||
"alreadyLicenseStatusChanged": "(de)ライセンス発行のキャンセルに失敗しました。選択された注文の状態が変更されています。画面を更新して再度ご確認ください。",
|
||||
"expiredSinceIssued": "(de)発行日から一定期間経過しているため、発行キャンセルできません。",
|
||||
"alreadyLicenseAllocated": "(de)発行したライセンスがすでに割り当てられたため、発行キャンセルできません。"
|
||||
}
|
||||
},
|
||||
"allocateLicensePopupPage": {
|
||||
@ -364,7 +370,19 @@
|
||||
"return": "(de)Return",
|
||||
"addGroup": "(de)Add Group",
|
||||
"groupName": "(de)Group Name",
|
||||
"edit": "(de)Edit"
|
||||
"edit": "(de)Edit",
|
||||
"addTypistGroup": "(de)Add Transcriptionist Group",
|
||||
"transcriptionist": "(de)Transcriptionist",
|
||||
"selected": "(de)Selected",
|
||||
"pool": "(de)Pool",
|
||||
"add": "(de)Add",
|
||||
"remove": "(de)Remove",
|
||||
"editTypistGroup": "(de)Edit Transcriptionist Group",
|
||||
"save": "(de)Save"
|
||||
},
|
||||
"message": {
|
||||
"selectedTypistEmptyError": "(de)TranscriptionistがいないTranscriptionistGroupは保存できません。1名以上をTranscriptionistとして選択してください。",
|
||||
"groupSaveFailedError": "(de)TypistGroupの保存に失敗しました。画面を更新し、再度実行してください"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
{
|
||||
"common": {
|
||||
"message": {
|
||||
"inputEmptyError": "Error Message",
|
||||
"inputEmptyError": "この項目の入力は必須です。入力してください。",
|
||||
"passwordIncorrectError": "Error Message",
|
||||
"emailIncorrectError": "Error Message",
|
||||
"internalServerError": "処理に失敗しました。時間をおいて再実行しても解決しない場合はシステム管理者にお問い合わせください。",
|
||||
"listEmpty": "検索結果が0件です。",
|
||||
"dialogConfirm": "操作を実行しますか?",
|
||||
"success": "処理に成功しました。"
|
||||
"success": "処理に成功しました。",
|
||||
"permissionDeniedError": "操作を実行する権限がありません。"
|
||||
},
|
||||
"label": {
|
||||
"cancel": "Cancel",
|
||||
@ -113,7 +114,8 @@
|
||||
"authorIdConflictError": "このAuthor IDは既に登録されています。他のAuthor IDで登録してください。",
|
||||
"authorIdIncorrectError": "Author IDの形式が不正です。Author IDは半角英数字(大文字)と\"_\"のみ入力可能です。",
|
||||
"roleChangeError": "Roleの変更に失敗しました。画面を更新して再度ユーザー情報を取得してください。",
|
||||
"encryptionPasswordCorrectError": "EncryptionPasswordがルールを満たしていません。"
|
||||
"encryptionPasswordCorrectError": "EncryptionPasswordがルールを満たしていません。",
|
||||
"alreadyLicenseDeallocatedError": "すでにライセンス割り当てが解除されています。画面を更新して再度ご確認下さい。"
|
||||
},
|
||||
"label": {
|
||||
"title": "User",
|
||||
@ -235,7 +237,7 @@
|
||||
"deleteDictation": "Delete Dictation",
|
||||
"selectedTranscriptionist": "Selected",
|
||||
"poolTranscriptionist": "Pool",
|
||||
"saveChanges": "Save changes"
|
||||
"saveChanges": "Save"
|
||||
}
|
||||
},
|
||||
"cardLicenseIssuePopupPage": {
|
||||
@ -328,7 +330,11 @@
|
||||
},
|
||||
"message": {
|
||||
"notEnoughOfNumberOfLicense": "ライセンスが不足しているため、発行することができませんでした。ライセンスの注文を行ってください。",
|
||||
"alreadyIssueLicense": "すでに発行済みの注文です。画面を更新してください。"
|
||||
"alreadyIssueLicense": "すでに発行済みの注文です。画面を更新してください。",
|
||||
"alreadyLicenseIssueOrCancel": "ライセンス注文のキャンセルに失敗しました。選択された注文はすでに発行またはキャンセルされています。画面を更新して再度ご確認ください。",
|
||||
"alreadyLicenseStatusChanged": "ライセンス発行のキャンセルに失敗しました。選択された注文の状態が変更されています。画面を更新して再度ご確認ください。",
|
||||
"expiredSinceIssued": "発行日から一定期間経過しているため、発行キャンセルできません。",
|
||||
"alreadyLicenseAllocated": "発行したライセンスがすでに割り当てられたため、発行キャンセルできません。"
|
||||
}
|
||||
},
|
||||
"allocateLicensePopupPage": {
|
||||
@ -364,7 +370,19 @@
|
||||
"return": "Return",
|
||||
"addGroup": "Add Group",
|
||||
"groupName": "Group Name",
|
||||
"edit": "Edit"
|
||||
"edit": "Edit",
|
||||
"addTypistGroup": "Add Transcriptionist Group",
|
||||
"transcriptionist": "Transcriptionist",
|
||||
"selected": "Selected",
|
||||
"pool": "Pool",
|
||||
"add": "Add",
|
||||
"remove": "Remove",
|
||||
"editTypistGroup": "Edit Transcriptionist Group",
|
||||
"save": "Save"
|
||||
},
|
||||
"message": {
|
||||
"selectedTypistEmptyError": "TranscriptionistがいないTranscriptionistGroupは保存できません。1名以上をTranscriptionistとして選択してください。",
|
||||
"groupSaveFailedError": "TypistGroupの保存に失敗しました。画面を更新し、再度実行してください"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
{
|
||||
"common": {
|
||||
"message": {
|
||||
"inputEmptyError": "(es)Error Message",
|
||||
"inputEmptyError": "(es)この項目の入力は必須です。入力してください。",
|
||||
"passwordIncorrectError": "(es)Error Message",
|
||||
"emailIncorrectError": "(es)Error Message",
|
||||
"internalServerError": "(es)処理に失敗しました。時間をおいて再実行しても解決しない場合はシステム管理者にお問い合わせください。",
|
||||
"listEmpty": "(es)検索結果が0件です。",
|
||||
"dialogConfirm": "(es)操作を実行しますか?",
|
||||
"success": "(es)処理に成功しました。"
|
||||
"success": "(es)処理に成功しました。",
|
||||
"permissionDeniedError": "(es)操作を実行する権限がありません。"
|
||||
},
|
||||
"label": {
|
||||
"cancel": "(es)Cancel",
|
||||
@ -113,7 +114,8 @@
|
||||
"authorIdConflictError": "(es)このAuthor IDは既に登録されています。他のAuthor IDで登録してください。",
|
||||
"authorIdIncorrectError": "(es)Author IDの形式が不正です。Author IDは半角英数字(大文字)と\"_\"のみ入力可能です。",
|
||||
"roleChangeError": "(es)Roleの変更に失敗しました。画面を更新して再度ユーザー情報を取得してください。",
|
||||
"encryptionPasswordCorrectError": "(es)EncryptionPasswordがルールを満たしていません。"
|
||||
"encryptionPasswordCorrectError": "(es)EncryptionPasswordがルールを満たしていません。",
|
||||
"alreadyLicenseDeallocatedError": "(es)すでにライセンス割り当てが解除されています。画面を更新して再度ご確認下さい。"
|
||||
},
|
||||
"label": {
|
||||
"title": "(es)User",
|
||||
@ -235,7 +237,7 @@
|
||||
"deleteDictation": "(es)Delete Dictation",
|
||||
"selectedTranscriptionist": "(es)Selected",
|
||||
"poolTranscriptionist": "(es)Pool",
|
||||
"saveChanges": "(es)Save changes"
|
||||
"saveChanges": "(es)Save"
|
||||
}
|
||||
},
|
||||
"cardLicenseIssuePopupPage": {
|
||||
@ -328,7 +330,11 @@
|
||||
},
|
||||
"message": {
|
||||
"notEnoughOfNumberOfLicense": "(es)ライセンスが不足しているため、発行することができませんでした。ライセンスの注文を行ってください。",
|
||||
"alreadyIssueLicense": "(es)すでに発行済みの注文です。画面を更新してください。"
|
||||
"alreadyIssueLicense": "(es)すでに発行済みの注文です。画面を更新してください。",
|
||||
"alreadyLicenseIssueOrCancel": "(es)ライセンス注文のキャンセルに失敗しました。選択された注文はすでに発行またはキャンセルされています。画面を更新して再度ご確認ください。",
|
||||
"alreadyLicenseStatusChanged": "(es)ライセンス発行のキャンセルに失敗しました。選択された注文の状態が変更されています。画面を更新して再度ご確認ください。",
|
||||
"expiredSinceIssued": "(es)発行日から一定期間経過しているため、発行キャンセルできません。",
|
||||
"alreadyLicenseAllocated": "(es)発行したライセンスがすでに割り当てられたため、発行キャンセルできません。"
|
||||
}
|
||||
},
|
||||
"allocateLicensePopupPage": {
|
||||
@ -364,7 +370,19 @@
|
||||
"return": "(es)Return",
|
||||
"addGroup": "(es)Add Group",
|
||||
"groupName": "(es)Group Name",
|
||||
"edit": "(es)Edit"
|
||||
"edit": "(es)Edit",
|
||||
"addTypistGroup": "(es)Add Transcriptionist Group",
|
||||
"transcriptionist": "(es)Transcriptionist",
|
||||
"selected": "(es)Selected",
|
||||
"pool": "(es)Pool",
|
||||
"add": "(es)Add",
|
||||
"remove": "(es)Remove",
|
||||
"editTypistGroup": "(es)Edit Transcriptionist Group",
|
||||
"save": "(es)Save"
|
||||
},
|
||||
"message": {
|
||||
"selectedTypistEmptyError": "(es)TranscriptionistがいないTranscriptionistGroupは保存できません。1名以上をTranscriptionistとして選択してください。",
|
||||
"groupSaveFailedError": "(es)TypistGroupの保存に失敗しました。画面を更新し、再度実行してください"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
{
|
||||
"common": {
|
||||
"message": {
|
||||
"inputEmptyError": "(fr)Error Message",
|
||||
"inputEmptyError": "(fr)この項目の入力は必須です。入力してください。",
|
||||
"passwordIncorrectError": "(fr)Error Message",
|
||||
"emailIncorrectError": "(fr)Error Message",
|
||||
"internalServerError": "(fr)処理に失敗しました。時間をおいて再実行しても解決しない場合はシステム管理者にお問い合わせください。",
|
||||
"listEmpty": "(fr)検索結果が0件です。",
|
||||
"dialogConfirm": "(fr)操作を実行しますか?",
|
||||
"success": "(fr)処理に成功しました。"
|
||||
"success": "(fr)処理に成功しました。",
|
||||
"permissionDeniedError": "(fr)操作を実行する権限がありません。"
|
||||
},
|
||||
"label": {
|
||||
"cancel": "(fr)Cancel",
|
||||
@ -113,7 +114,8 @@
|
||||
"authorIdConflictError": "(fr)このAuthor IDは既に登録されています。他のAuthor IDで登録してください。",
|
||||
"authorIdIncorrectError": "(fr)Author IDの形式が不正です。Author IDは半角英数字(大文字)と\"_\"のみ入力可能です。",
|
||||
"roleChangeError": "(fr)Roleの変更に失敗しました。画面を更新して再度ユーザー情報を取得してください。",
|
||||
"encryptionPasswordCorrectError": "(fr)EncryptionPasswordがルールを満たしていません。"
|
||||
"encryptionPasswordCorrectError": "(fr)EncryptionPasswordがルールを満たしていません。",
|
||||
"alreadyLicenseDeallocatedError": "(fr)すでにライセンス割り当てが解除されています。画面を更新して再度ご確認下さい。"
|
||||
},
|
||||
"label": {
|
||||
"title": "(fr)User",
|
||||
@ -235,7 +237,7 @@
|
||||
"deleteDictation": "(fr)Delete Dictation",
|
||||
"selectedTranscriptionist": "(fr)Selected",
|
||||
"poolTranscriptionist": "(fr)Pool",
|
||||
"saveChanges": "(fr)Save changes"
|
||||
"saveChanges": "(fr)Save"
|
||||
}
|
||||
},
|
||||
"cardLicenseIssuePopupPage": {
|
||||
@ -328,7 +330,11 @@
|
||||
},
|
||||
"message": {
|
||||
"notEnoughOfNumberOfLicense": "(fr)ライセンスが不足しているため、発行することができませんでした。ライセンスの注文を行ってください。",
|
||||
"alreadyIssueLicense": "(fr)すでに発行済みの注文です。画面を更新してください。"
|
||||
"alreadyIssueLicense": "(fr)すでに発行済みの注文です。画面を更新してください。",
|
||||
"alreadyLicenseIssueOrCancel": "(fr)ライセンス注文のキャンセルに失敗しました。選択された注文はすでに発行またはキャンセルされています。画面を更新して再度ご確認ください。",
|
||||
"alreadyLicenseStatusChanged": "(fr)ライセンス発行のキャンセルに失敗しました。選択された注文の状態が変更されています。画面を更新して再度ご確認ください。",
|
||||
"expiredSinceIssued": "(fr)発行日から一定期間経過しているため、発行キャンセルできません。",
|
||||
"alreadyLicenseAllocated": "(fr)発行したライセンスがすでに割り当てられたため、発行キャンセルできません。"
|
||||
}
|
||||
},
|
||||
"allocateLicensePopupPage": {
|
||||
@ -364,7 +370,19 @@
|
||||
"return": "(fr)Return",
|
||||
"addGroup": "(fr)Add Group",
|
||||
"groupName": "(fr)Group Name",
|
||||
"edit": "(fr)Edit"
|
||||
"edit": "(fr)Edit",
|
||||
"addTypistGroup": "(fr)Add Transcriptionist Group",
|
||||
"transcriptionist": "(fr)Transcriptionist",
|
||||
"selected": "(fr)Selected",
|
||||
"pool": "(fr)Pool",
|
||||
"add": "(fr)Add",
|
||||
"remove": "(fr)Remove",
|
||||
"editTypistGroup": "(fr)Edit Transcriptionist Group",
|
||||
"save": "(fr)Save"
|
||||
},
|
||||
"message": {
|
||||
"selectedTypistEmptyError": "(fr)TranscriptionistがいないTranscriptionistGroupは保存できません。1名以上をTranscriptionistとして選択してください。",
|
||||
"groupSaveFailedError": "(fr)TypistGroupの保存に失敗しました。画面を更新し、再度実行してください"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
16
dictation_server/db/migrations/030-drop_licenses_history.sql
Normal file
16
dictation_server/db/migrations/030-drop_licenses_history.sql
Normal file
@ -0,0 +1,16 @@
|
||||
-- +migrate Up
|
||||
DROP TABLE IF EXISTS `licenses_history`;
|
||||
|
||||
-- +migrate Down
|
||||
CREATE TABLE IF NOT EXISTS `licenses_history` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY COMMENT 'ライセンス履歴ID',
|
||||
`user_id` BIGINT UNSIGNED NOT NULL COMMENT 'ユーザID',
|
||||
`license_id` BIGINT UNSIGNED NOT NULL COMMENT 'ライセンスID',
|
||||
`allocated` BOOLEAN NOT NULL COMMENT '割り当てたか',
|
||||
`executed_at` TIMESTAMP NOT NULL COMMENT '実施日時',
|
||||
`exchange_type` VARCHAR(255) NOT NULL COMMENT 'ライセンス切り替え種別(なし/トライアル→通常/紙→通常)',
|
||||
`created_at` TIMESTAMP DEFAULT now() COMMENT '作成時刻',
|
||||
`created_by` VARCHAR(255) COMMENT '作成者',
|
||||
`updated_at` TIMESTAMP DEFAULT now() on UPDATE now() COMMENT '更新時刻',
|
||||
`updated_by` VARCHAR(255) COMMENT '更新者'
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci;
|
||||
16
dictation_server/db/migrations/031-create_worktypes.sql
Normal file
16
dictation_server/db/migrations/031-create_worktypes.sql
Normal file
@ -0,0 +1,16 @@
|
||||
-- +migrate Up
|
||||
CREATE TABLE IF NOT EXISTS `worktypes` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY COMMENT 'worktypeの内部ID',
|
||||
`account_id` BIGINT UNSIGNED NOT NULL COMMENT 'アカウントID',
|
||||
`custom_worktype_id` VARCHAR(255) NOT NULL COMMENT 'ユーザーが決めるWorktypeID',
|
||||
`description` VARCHAR(255) COMMENT 'Worktypeの説明',
|
||||
`deleted_at` TIMESTAMP COMMENT '削除時刻',
|
||||
`created_by` VARCHAR(255) COMMENT '作成者',
|
||||
`created_at` TIMESTAMP DEFAULT now() COMMENT '作成時刻',
|
||||
`updated_by` VARCHAR(255) COMMENT '更新者',
|
||||
`updated_at` TIMESTAMP DEFAULT now() on UPDATE now() COMMENT '更新時刻',
|
||||
UNIQUE custom_worktype_id_index (custom_worktype_id, account_id)
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci;
|
||||
|
||||
-- +migrate Down
|
||||
DROP TABLE `worktypes`;
|
||||
@ -354,6 +354,120 @@
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/accounts/typist-groups/{typistGroupId}": {
|
||||
"get": {
|
||||
"operationId": "getTypistGroup",
|
||||
"summary": "",
|
||||
"description": "ログインしているユーザーのアカウント配下でIDで指定されたタイピストグループを取得します",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "typistGroupId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": { "type": "number" }
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功時のレスポンス",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetTypistGroupResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": ["accounts"],
|
||||
"security": [{ "bearer": [] }]
|
||||
},
|
||||
"post": {
|
||||
"operationId": "updateTypistGroup",
|
||||
"summary": "",
|
||||
"description": "ログインしているユーザーのアカウント配下でIDで指定されたタイピストグループを更新します",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "typistGroupId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": { "type": "number" }
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UpdateTypistGroupRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功時のレスポンス",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CreateTypistGroupResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": ["accounts"],
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/accounts/partner": {
|
||||
"post": {
|
||||
"operationId": "createPartnerAccount",
|
||||
@ -582,6 +696,148 @@
|
||||
"tags": ["accounts"]
|
||||
}
|
||||
},
|
||||
"/accounts/issue/cancel": {
|
||||
"post": {
|
||||
"operationId": "cancelIssue",
|
||||
"summary": "",
|
||||
"description": "ライセンス発行をキャンセルします",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/CancelIssueRequest" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功時のレスポンス",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/CancelIssueResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "対象注文のステータスが発行済以外/発行日から15日以降/ライセンスをユーザに割り当てている",
|
||||
"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": ["accounts"],
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/accounts/worktypes": {
|
||||
"get": {
|
||||
"operationId": "getWorktypes",
|
||||
"summary": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功時のレスポンス",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetWorktypesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "認証エラー",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "想定外のサーバーエラー",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["accounts"],
|
||||
"security": [{ "bearer": [] }]
|
||||
},
|
||||
"post": {
|
||||
"operationId": "createWorktype",
|
||||
"summary": "",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CreateWorktypesRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功時のレスポンス",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CreateWorktypeResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "WorktypeIDが重複 / WorktypeIDが空 / WorktypeIDが20件登録済み",
|
||||
"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": ["accounts"],
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/users/confirm": {
|
||||
"post": {
|
||||
"operationId": "confirmUser",
|
||||
@ -2020,6 +2276,58 @@
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/licenses/orders/cancel": {
|
||||
"post": {
|
||||
"operationId": "cancelOrder",
|
||||
"summary": "",
|
||||
"description": "ライセンス注文をキャンセルします",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/CancelOrderRequest" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功時のレスポンス",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/CancelOrderResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": ["licenses"],
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/notification/register": {
|
||||
"post": {
|
||||
"operationId": "register",
|
||||
@ -2231,6 +2539,14 @@
|
||||
},
|
||||
"required": ["typistGroups"]
|
||||
},
|
||||
"GetTypistGroupResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"typistGroupName": { "type": "string" },
|
||||
"typistIds": { "type": "array", "items": { "type": "integer" } }
|
||||
},
|
||||
"required": ["typistGroupName", "typistIds"]
|
||||
},
|
||||
"CreateTypistGroupRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -2242,12 +2558,28 @@
|
||||
"typistIds": {
|
||||
"minItems": 1,
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
"items": { "type": "integer" }
|
||||
}
|
||||
},
|
||||
"required": ["typistGroupName", "typistIds"]
|
||||
},
|
||||
"CreateTypistGroupResponse": { "type": "object", "properties": {} },
|
||||
"UpdateTypistGroupRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"typistGroupName": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 50
|
||||
},
|
||||
"typistIds": {
|
||||
"minItems": 1,
|
||||
"type": "array",
|
||||
"items": { "type": "integer" }
|
||||
}
|
||||
},
|
||||
"required": ["typistGroupName", "typistIds"]
|
||||
},
|
||||
"CreatePartnerAccountRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -2391,6 +2723,50 @@
|
||||
},
|
||||
"required": ["dealers"]
|
||||
},
|
||||
"CancelIssueRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"orderedAccountId": {
|
||||
"type": "number",
|
||||
"description": "注文元アカウントID"
|
||||
},
|
||||
"poNumber": { "type": "string", "description": "POナンバー" }
|
||||
},
|
||||
"required": ["orderedAccountId", "poNumber"]
|
||||
},
|
||||
"CancelIssueResponse": { "type": "object", "properties": {} },
|
||||
"Worktype": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "number", "description": "WorktypeのID" },
|
||||
"worktypeId": { "type": "string", "description": "WorktypeID" },
|
||||
"description": { "type": "string", "description": "Worktypeの説明" }
|
||||
},
|
||||
"required": ["id", "worktypeId"]
|
||||
},
|
||||
"GetWorktypesResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"worktypes": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/components/schemas/Worktype" }
|
||||
}
|
||||
},
|
||||
"required": ["worktypes"]
|
||||
},
|
||||
"CreateWorktypesRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"worktypeId": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"description": "WorktypeID"
|
||||
},
|
||||
"description": { "type": "string", "description": "Worktypeの説明" }
|
||||
},
|
||||
"required": ["worktypeId"]
|
||||
},
|
||||
"CreateWorktypeResponse": { "type": "object", "properties": {} },
|
||||
"ConfirmRequest": {
|
||||
"type": "object",
|
||||
"properties": { "token": { "type": "string" } },
|
||||
@ -2929,6 +3305,12 @@
|
||||
},
|
||||
"required": ["allocatableLicenses"]
|
||||
},
|
||||
"CancelOrderRequest": {
|
||||
"type": "object",
|
||||
"properties": { "poNumber": { "type": "string" } },
|
||||
"required": ["poNumber"]
|
||||
},
|
||||
"CancelOrderResponse": { "type": "object", "properties": {} },
|
||||
"RegisterRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@ -40,6 +40,7 @@ import { CheckoutPermissionsRepositoryModule } from './repositories/checkout_per
|
||||
import { UserGroupsRepositoryModule } from './repositories/user_groups/user_groups.repository.module';
|
||||
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';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -94,6 +95,7 @@ import { TemplateFilesRepositoryModule } from './repositories/template_files/tem
|
||||
BlobstorageModule,
|
||||
AuthGuardsModule,
|
||||
SortCriteriaRepositoryModule,
|
||||
WorktypesRepositoryModule,
|
||||
],
|
||||
controllers: [
|
||||
HealthController,
|
||||
|
||||
@ -46,4 +46,7 @@ export const ErrorCodes = [
|
||||
'E010804', // ライセンス不足エラー
|
||||
'E010805', // ライセンス有効期限切れエラー
|
||||
'E010806', // ライセンス割り当て不可エラー
|
||||
'E010807', // ライセンス割り当て解除済みエラー
|
||||
'E010808', // ライセンス注文キャンセル不可エラー
|
||||
'E010908', // タイピストグループ不在エラー
|
||||
] as const;
|
||||
|
||||
@ -35,4 +35,7 @@ export const errors: Errors = {
|
||||
E010804: 'License shortage Error',
|
||||
E010805: 'License is expired Error',
|
||||
E010806: 'License is unavailable Error',
|
||||
E010807: 'License is already deallocated Error',
|
||||
E010808: 'Order cancel failed Error',
|
||||
E010908: 'Typist Group not exist Error',
|
||||
};
|
||||
|
||||
@ -17,6 +17,7 @@ import { LicensesRepositoryModule } from '../../repositories/licenses/licenses.r
|
||||
import { AudioFilesRepositoryModule } from '../../repositories/audio_files/audio_files.repository.module';
|
||||
import { AudioOptionItemsRepositoryModule } from '../../repositories/audio_option_items/audio_option_items.repository.module';
|
||||
import { CheckoutPermissionsRepositoryModule } from '../../repositories/checkout_permissions/checkout_permissions.repository.module';
|
||||
import { WorktypesRepositoryModule } from '../../repositories/worktypes/worktypes.repository.module';
|
||||
import { NotificationModule } from '../../features//notification/notification.module';
|
||||
import { NotificationhubModule } from '../../gateways/notificationhub/notificationhub.module';
|
||||
import { BlobstorageModule } from '../../gateways/blobstorage/blobstorage.module';
|
||||
@ -63,6 +64,7 @@ export const makeTestingModule = async (
|
||||
BlobstorageModule,
|
||||
AuthGuardsModule,
|
||||
SortCriteriaRepositoryModule,
|
||||
WorktypesRepositoryModule,
|
||||
],
|
||||
providers: [
|
||||
AuthService,
|
||||
|
||||
363
dictation_server/src/common/test/utility.ts
Normal file
363
dictation_server/src/common/test/utility.ts
Normal file
@ -0,0 +1,363 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { User } from '../../repositories/users/entity/user.entity';
|
||||
import { Account } from '../../repositories/accounts/entity/account.entity';
|
||||
import { ADMIN_ROLES, USER_ROLES } from '../../constants';
|
||||
|
||||
type InitialTestDBState = {
|
||||
tier1Accounts: { account: Account; users: User[] }[];
|
||||
tier2Accounts: { account: Account; users: User[] }[];
|
||||
tier3Accounts: { account: Account; users: User[] }[];
|
||||
tier4Accounts: { account: Account; users: User[] }[];
|
||||
tier5Accounts: { account: Account; users: User[] }[];
|
||||
};
|
||||
|
||||
// 上書きされたら困る項目を除外したAccount型
|
||||
type OverrideAccount = Omit<
|
||||
Account,
|
||||
'id' | 'primary_admin_user_id' | 'secondary_admin_user_id' | 'user'
|
||||
>;
|
||||
|
||||
// 上書きされたら困る項目を除外したUser型
|
||||
type OverrideUser = Omit<
|
||||
User,
|
||||
'id' | 'account' | 'license' | 'userGroupMembers'
|
||||
>;
|
||||
|
||||
type AccountDefault = { [K in keyof OverrideAccount]?: OverrideAccount[K] };
|
||||
type UserDefault = { [K in keyof OverrideUser]?: OverrideUser[K] };
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: 1~4階層のアカウントとその管理者ユーザーを作成します
|
||||
* @param dataSource データソース
|
||||
* @returns 作成されたデータセット
|
||||
*/
|
||||
export const makeHierarchicalAccounts = async (
|
||||
datasource: DataSource,
|
||||
): Promise<InitialTestDBState> => {
|
||||
const state: InitialTestDBState = {
|
||||
tier1Accounts: [],
|
||||
tier2Accounts: [],
|
||||
tier3Accounts: [],
|
||||
tier4Accounts: [],
|
||||
tier5Accounts: [],
|
||||
};
|
||||
|
||||
// 第1階層を作成
|
||||
{
|
||||
const { account, admin } = await makeTestAccount(datasource, {
|
||||
tier: 1,
|
||||
company_name: 'OMDS',
|
||||
});
|
||||
|
||||
state.tier1Accounts.push({
|
||||
account: account,
|
||||
users: [admin],
|
||||
});
|
||||
}
|
||||
// 第2階層を作成
|
||||
{
|
||||
const { account: tier1 } = state.tier1Accounts.slice().shift();
|
||||
{
|
||||
const { account, admin } = await makeTestAccount(datasource, {
|
||||
tier: 2,
|
||||
parent_account_id: tier1.id,
|
||||
company_name: 'OMDS_US',
|
||||
});
|
||||
state.tier2Accounts.push({
|
||||
account: account,
|
||||
users: [admin],
|
||||
});
|
||||
}
|
||||
{
|
||||
const { account, admin } = await makeTestAccount(datasource, {
|
||||
tier: 2,
|
||||
parent_account_id: tier1.id,
|
||||
company_name: 'OMDS_EU',
|
||||
});
|
||||
state.tier2Accounts.push({
|
||||
account: account,
|
||||
users: [admin],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 第3階層を作成
|
||||
{
|
||||
for (const v of state.tier2Accounts) {
|
||||
{
|
||||
const { account, admin } = await makeTestAccount(datasource, {
|
||||
tier: 3,
|
||||
parent_account_id: v.account.id,
|
||||
company_name: `Agency_${v.account.id}_01`,
|
||||
});
|
||||
state.tier3Accounts.push({
|
||||
account: account,
|
||||
users: [admin],
|
||||
});
|
||||
}
|
||||
{
|
||||
const { account, admin } = await makeTestAccount(datasource, {
|
||||
tier: 3,
|
||||
parent_account_id: v.account.id,
|
||||
company_name: `Agency_${v.account.id}_02`,
|
||||
});
|
||||
state.tier3Accounts.push({
|
||||
account: account,
|
||||
users: [admin],
|
||||
});
|
||||
}
|
||||
}
|
||||
// 第4階層を作成
|
||||
for (const v of state.tier3Accounts) {
|
||||
{
|
||||
const { account, admin } = await makeTestAccount(datasource, {
|
||||
tier: 4,
|
||||
parent_account_id: v.account.id,
|
||||
company_name: `Distributor_${v.account.id}_01`,
|
||||
});
|
||||
state.tier4Accounts.push({
|
||||
account: account,
|
||||
users: [admin],
|
||||
});
|
||||
}
|
||||
{
|
||||
const { account, admin } = await makeTestAccount(datasource, {
|
||||
tier: 4,
|
||||
parent_account_id: v.account.id,
|
||||
company_name: `Distributor_${v.account.id}_02`,
|
||||
});
|
||||
state.tier4Accounts.push({
|
||||
account: account,
|
||||
users: [admin],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: 指定したプロパティを上書きしたアカウントとその管理者ユーザーを作成する
|
||||
* @param dataSource データソース
|
||||
* @param defaultUserValue Account型と同等かつoptionalなプロパティを持つ上書き箇所指定用オブジェクト
|
||||
* @param defaultAdminUserValue User型と同等かつoptionalなプロパティを持つ上書き箇所指定用オブジェクト(account_id等の所属関係が破壊される上書きは無視する)
|
||||
* @returns 作成したアカウント
|
||||
*/
|
||||
export const makeTestAccount = async (
|
||||
datasource: DataSource,
|
||||
defaultAccountValue?: AccountDefault,
|
||||
defaultAdminUserValue?: UserDefault,
|
||||
): Promise<{ account: Account; admin: User }> => {
|
||||
let accountId: number;
|
||||
let userId: number;
|
||||
{
|
||||
const d = defaultAccountValue;
|
||||
const { identifiers } = await datasource.getRepository(Account).insert({
|
||||
tier: d?.tier ?? 1,
|
||||
parent_account_id: d?.parent_account_id ?? undefined,
|
||||
country: d?.country ?? 'US',
|
||||
delegation_permission: d?.delegation_permission ?? false,
|
||||
locked: d?.locked ?? false,
|
||||
company_name: d?.company_name ?? 'test inc.',
|
||||
verified: d?.verified ?? true,
|
||||
deleted_at: d?.deleted_at ?? '',
|
||||
created_by: d?.created_by ?? 'test_runner',
|
||||
created_at: d?.created_at ?? new Date(),
|
||||
updated_by: d?.updated_by ?? 'updater',
|
||||
updated_at: d?.updated_at ?? new Date(),
|
||||
});
|
||||
const result = identifiers.pop() as Account;
|
||||
accountId = result.id;
|
||||
}
|
||||
{
|
||||
const d = defaultAdminUserValue;
|
||||
const { identifiers } = await datasource.getRepository(User).insert({
|
||||
external_id: d?.external_id ?? uuidv4(),
|
||||
account_id: accountId,
|
||||
role: d?.role ?? 'admin none',
|
||||
author_id: d?.author_id ?? undefined,
|
||||
accepted_terms_version: d?.accepted_terms_version ?? '1.0',
|
||||
email_verified: d?.email_verified ?? true,
|
||||
auto_renew: d?.auto_renew ?? true,
|
||||
license_alert: d?.license_alert ?? true,
|
||||
notification: d?.notification ?? true,
|
||||
encryption: d?.encryption ?? true,
|
||||
encryption_password: d?.encryption_password ?? 'password',
|
||||
prompt: d?.prompt ?? true,
|
||||
deleted_at: d?.deleted_at ?? '',
|
||||
created_by: d?.created_by ?? 'test_runner',
|
||||
created_at: d?.created_at ?? new Date(),
|
||||
updated_by: d?.updated_by ?? 'updater',
|
||||
updated_at: d?.updated_at ?? new Date(),
|
||||
});
|
||||
|
||||
const result = identifiers.pop() as User;
|
||||
userId = result.id;
|
||||
}
|
||||
|
||||
// Accountの管理者を設定する
|
||||
await datasource.getRepository(Account).update(
|
||||
{ id: accountId },
|
||||
{
|
||||
primary_admin_user_id: userId,
|
||||
},
|
||||
);
|
||||
|
||||
const account = await datasource.getRepository(Account).findOne({
|
||||
where: {
|
||||
id: accountId,
|
||||
},
|
||||
});
|
||||
|
||||
const admin = await datasource.getRepository(User).findOne({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
account: account,
|
||||
admin: admin,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: 指定したプロパティを上書きした管理者ユーザーの存在しないアカウントを作成する
|
||||
* @param dataSource データソース
|
||||
* @param defaultUserValue Account型と同等かつoptionalなプロパティを持つ上書き箇所指定用オブジェクト
|
||||
* @returns 作成したアカウント
|
||||
*/
|
||||
export const makeTestSimpleAccount = async (
|
||||
datasource: DataSource,
|
||||
defaultAccountValue?: AccountDefault,
|
||||
): Promise<Account> => {
|
||||
const d = defaultAccountValue;
|
||||
const { identifiers } = await datasource.getRepository(Account).insert({
|
||||
tier: d?.tier ?? 1,
|
||||
parent_account_id: d?.parent_account_id ?? undefined,
|
||||
country: d?.country ?? 'US',
|
||||
delegation_permission: d?.delegation_permission ?? false,
|
||||
locked: d?.locked ?? false,
|
||||
company_name: d?.company_name ?? 'test inc.',
|
||||
verified: d?.verified ?? true,
|
||||
deleted_at: d?.deleted_at ?? '',
|
||||
created_by: d?.created_by ?? 'test_runner',
|
||||
created_at: d?.created_at ?? new Date(),
|
||||
updated_by: d?.updated_by ?? 'updater',
|
||||
updated_at: d?.updated_at ?? new Date(),
|
||||
});
|
||||
const result = identifiers.pop() as Account;
|
||||
|
||||
const account = await datasource.getRepository(Account).findOne({
|
||||
where: {
|
||||
id: result.id,
|
||||
},
|
||||
});
|
||||
|
||||
return account;
|
||||
};
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: 指定したプロパティを上書きしたユーザーを作成する
|
||||
* @param dataSource データソース
|
||||
* @param defaultUserValue User型と同等かつoptionalなプロパティを持つ上書き箇所指定用オブジェクト
|
||||
* @returns 作成したユーザー
|
||||
*/
|
||||
export const makeTestUser = async (
|
||||
datasource: DataSource,
|
||||
defaultUserValue?: UserDefault,
|
||||
): Promise<User> => {
|
||||
const d = defaultUserValue;
|
||||
const { identifiers } = await datasource.getRepository(User).insert({
|
||||
account_id: d?.account_id ?? -1,
|
||||
external_id: d?.external_id ?? uuidv4(),
|
||||
role: d?.role ?? `${ADMIN_ROLES.STANDARD} ${USER_ROLES.NONE}`,
|
||||
author_id: d?.author_id,
|
||||
accepted_terms_version: d?.accepted_terms_version,
|
||||
email_verified: d?.email_verified ?? true,
|
||||
auto_renew: d?.auto_renew ?? true,
|
||||
license_alert: d?.license_alert ?? true,
|
||||
notification: d?.notification ?? true,
|
||||
encryption: d?.encryption ?? true,
|
||||
encryption_password: d?.encryption_password,
|
||||
prompt: d?.prompt ?? true,
|
||||
created_by: d?.created_by ?? 'test_runner',
|
||||
created_at: d?.created_at ?? new Date(),
|
||||
updated_by: d?.updated_by ?? 'updater',
|
||||
updated_at: d?.updated_at ?? new Date(),
|
||||
});
|
||||
const result = identifiers.pop() as User;
|
||||
|
||||
return await datasource.getRepository(User).findOne({
|
||||
where: {
|
||||
id: result.id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: 指定IDのアカウントを取得する
|
||||
* @param dataSource データソース
|
||||
* @param id アカウントID
|
||||
* @returns 該当アカウント
|
||||
*/
|
||||
export const getAccount = async (dataSource: DataSource, id: number) => {
|
||||
return await dataSource.getRepository(Account).findOne({
|
||||
where: { id: id },
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: すべてのアカウントを取得する
|
||||
* @param dataSource データソース
|
||||
* @returns 該当アカウント一覧
|
||||
*/
|
||||
export const getAccounts = async (
|
||||
dataSource: DataSource,
|
||||
): Promise<Account[]> => {
|
||||
return await dataSource.getRepository(Account).find();
|
||||
};
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: 指定ExternalIdのユーザーを取得する
|
||||
* @param dataSource データソース
|
||||
* @param externalId 外部ID
|
||||
* @returns 該当ユーザー
|
||||
*/
|
||||
export const getUserFromExternalId = async (
|
||||
dataSource: DataSource,
|
||||
externalId: string,
|
||||
) => {
|
||||
return await dataSource.getRepository(User).findOne({
|
||||
where: { external_id: externalId },
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: 指定Idのユーザーを取得する
|
||||
* @param dataSource データソース
|
||||
* @param externalId 外部ID
|
||||
* @returns 該当ユーザー
|
||||
*/
|
||||
export const getUser = async (
|
||||
datasource: DataSource,
|
||||
id: number,
|
||||
): Promise<User> => {
|
||||
const user = await datasource.getRepository(User).findOne({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
});
|
||||
return user;
|
||||
};
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: すべてのユーザーを取得する
|
||||
* @param dataSource データソース
|
||||
* @returns 該当ユーザー一覧
|
||||
*/
|
||||
export const getUsers = async (dataSource: DataSource): Promise<User[]> => {
|
||||
return await dataSource.getRepository(User).find();
|
||||
};
|
||||
36
dictation_server/src/common/validators/IsUnique.validator.ts
Normal file
36
dictation_server/src/common/validators/IsUnique.validator.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import {
|
||||
ValidatorConstraint,
|
||||
ValidatorConstraintInterface,
|
||||
ValidationArguments,
|
||||
ValidationOptions,
|
||||
registerDecorator,
|
||||
} from 'class-validator';
|
||||
|
||||
@ValidatorConstraint()
|
||||
export class IsUniqueArray implements ValidatorConstraintInterface {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
validate(arr: any[], args: ValidationArguments) {
|
||||
return arr.length === new Set(arr).size;
|
||||
}
|
||||
|
||||
defaultMessage(args: ValidationArguments) {
|
||||
return `${args.property} should be an array of unique values`;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 配列内の値がすべてユニークであるかをチェックする
|
||||
* @param [validationOptions]
|
||||
* @returns
|
||||
*/
|
||||
export function IsUnique(validationOptions?: ValidationOptions) {
|
||||
return function (object: object, propertyName: string) {
|
||||
registerDecorator({
|
||||
name: 'isUnique',
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
constraints: [],
|
||||
options: validationOptions,
|
||||
validator: IsUniqueArray,
|
||||
});
|
||||
};
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
import { registerDecorator, ValidationOptions } from 'class-validator';
|
||||
|
||||
// TODO タスク 2502: バリデータをクラスを使用した記述に統一するで修正する
|
||||
export const IsAdminPasswordvalid = (validationOptions?: ValidationOptions) => {
|
||||
return (object: any, propertyName: string) => {
|
||||
registerDecorator({
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
ValidationArguments,
|
||||
} from 'class-validator';
|
||||
import { Assignee } from '../../features/tasks/types/types';
|
||||
// TODO タスク 2502: バリデータをクラスを使用した記述に統一するで修正する
|
||||
/**
|
||||
* Validations options
|
||||
* @param [validationOptions]
|
||||
|
||||
@ -4,7 +4,7 @@ import {
|
||||
ValidationOptions,
|
||||
} from 'class-validator';
|
||||
import { SignupRequest } from '../../features/users/types/types';
|
||||
|
||||
// TODO タスク 2502: バリデータをクラスを使用した記述に統一するで修正する
|
||||
export const IsPasswordvalid = (validationOptions?: ValidationOptions) => {
|
||||
return (object: any, propertyName: string) => {
|
||||
registerDecorator({
|
||||
@ -35,7 +35,7 @@ export const IsPasswordvalid = (validationOptions?: ValidationOptions) => {
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
// TODO タスク 2502: バリデータをクラスを使用した記述に統一するで修正する
|
||||
export const IsEncryptionPasswordPresent = (
|
||||
validationOptions?: ValidationOptions,
|
||||
) => {
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
} from '../../features/users/types/types';
|
||||
import { USER_ROLES } from '../../constants';
|
||||
|
||||
// TODO タスク 2502: バリデータをクラスを使用した記述に統一するで修正する
|
||||
export const IsRoleAuthorDataValid = <
|
||||
T extends SignupRequest | PostUpdateUserRequest,
|
||||
>(
|
||||
|
||||
@ -89,16 +89,14 @@ export const USER_ROLES = {
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* ライセンス注文ステータス(発行待ち)
|
||||
* @const {string}
|
||||
* ライセンス注文状態
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const LICENSE_STATUS_ISSUE_REQUESTING = 'Issue Requesting';
|
||||
|
||||
/**
|
||||
* ライセンス注文ステータス(発行済み)
|
||||
* @const {string}
|
||||
*/
|
||||
export const LICENSE_STATUS_ISSUED = 'Issued';
|
||||
export const LICENSE_ISSUE_STATUS = {
|
||||
ISSUE_REQUESTING: 'Issue Requesting',
|
||||
ISSUED: 'Issued',
|
||||
CANCELED: 'Order Canceled',
|
||||
};
|
||||
|
||||
/**
|
||||
* ライセンス種別
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
Get,
|
||||
Req,
|
||||
UseGuards,
|
||||
Param,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiOperation,
|
||||
@ -35,6 +36,15 @@ import {
|
||||
GetDealersResponse,
|
||||
CreateTypistGroupResponse,
|
||||
CreateTypistGroupRequest,
|
||||
GetTypistGroupResponse,
|
||||
GetTypistGroupRequest,
|
||||
UpdateTypistGroupRequest,
|
||||
UpdateTypistGroupRequestParam,
|
||||
CancelIssueRequest,
|
||||
CancelIssueResponse,
|
||||
GetWorktypesResponse,
|
||||
CreateWorktypeResponse,
|
||||
CreateWorktypesRequest,
|
||||
} from './types/types';
|
||||
import { USER_ROLES, ADMIN_ROLES, TIERS } from '../../constants';
|
||||
import { AuthGuard } from '../../common/guards/auth/authguards';
|
||||
@ -127,9 +137,6 @@ export class AccountsController {
|
||||
@Req() req: Request,
|
||||
@Body() body: GetLicenseSummaryRequest,
|
||||
): Promise<GetLicenseSummaryResponse> {
|
||||
console.log(req.header('Authorization'));
|
||||
console.log(body);
|
||||
|
||||
const response = await this.accountService.getLicenseSummary(
|
||||
body.accountId,
|
||||
);
|
||||
@ -165,8 +172,6 @@ export class AccountsController {
|
||||
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
|
||||
@Get('me')
|
||||
async getMyAccount(@Req() req: Request): Promise<GetMyAccountResponse> {
|
||||
console.log(req.header('Authorization'));
|
||||
|
||||
// アクセストークン取得
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
@ -199,8 +204,6 @@ export class AccountsController {
|
||||
@UseGuards(AuthGuard)
|
||||
@Get('typists')
|
||||
async getTypists(@Req() req: Request): Promise<GetTypistsResponse> {
|
||||
console.log(req.header('Authorization'));
|
||||
// アクセストークン取得
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
|
||||
@ -233,8 +236,6 @@ export class AccountsController {
|
||||
@UseGuards(AuthGuard)
|
||||
@Get('typist-groups')
|
||||
async getTypistGroups(@Req() req: Request): Promise<GetTypistGroupsResponse> {
|
||||
console.log(req.header('Authorization'));
|
||||
// アクセストークン取得
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
|
||||
@ -245,6 +246,56 @@ export class AccountsController {
|
||||
return { typistGroups };
|
||||
}
|
||||
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: GetTypistGroupResponse,
|
||||
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: 'getTypistGroup',
|
||||
description:
|
||||
'ログインしているユーザーのアカウント配下でIDで指定されたタイピストグループを取得します',
|
||||
})
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
|
||||
@Get('typist-groups/:typistGroupId')
|
||||
async getTypistGroup(
|
||||
@Req() req: Request,
|
||||
@Param() param: GetTypistGroupRequest,
|
||||
): Promise<GetTypistGroupResponse> {
|
||||
const { typistGroupId } = param;
|
||||
|
||||
// アクセストークン取得
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
const typistGroup = await this.accountService.getTypistGroup(
|
||||
context,
|
||||
userId,
|
||||
typistGroupId,
|
||||
);
|
||||
|
||||
return typistGroup;
|
||||
}
|
||||
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: CreateTypistGroupResponse,
|
||||
@ -278,9 +329,70 @@ export class AccountsController {
|
||||
@Req() req: Request,
|
||||
@Body() body: CreateTypistGroupRequest,
|
||||
): Promise<CreateTypistGroupResponse> {
|
||||
const { typistGroupName, typistIds } = body;
|
||||
// アクセストークン取得
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const context = makeContext(userId);
|
||||
await this.accountService.createTypistGroup(
|
||||
context,
|
||||
userId,
|
||||
typistGroupName,
|
||||
typistIds,
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: CreateTypistGroupResponse,
|
||||
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: 'updateTypistGroup',
|
||||
description:
|
||||
'ログインしているユーザーのアカウント配下でIDで指定されたタイピストグループを更新します',
|
||||
})
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
|
||||
@Post('typist-groups/:typistGroupId')
|
||||
async updateTypistGroup(
|
||||
@Req() req: Request,
|
||||
@Body() body: UpdateTypistGroupRequest,
|
||||
@Param() param: UpdateTypistGroupRequestParam,
|
||||
): Promise<CreateTypistGroupResponse> {
|
||||
const { typistGroupName, typistIds } = body;
|
||||
const { typistGroupId } = param;
|
||||
|
||||
// アクセストークン取得
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
await this.accountService.updateTypistGroup(
|
||||
context,
|
||||
userId,
|
||||
typistGroupId,
|
||||
typistGroupName,
|
||||
typistIds,
|
||||
);
|
||||
|
||||
return {};
|
||||
}
|
||||
@ -446,8 +558,6 @@ export class AccountsController {
|
||||
@Req() req: Request,
|
||||
@Body() body: IssueLicenseRequest,
|
||||
): Promise<IssueLicenseResponse> {
|
||||
console.log(req.header('Authorization'));
|
||||
console.log(body);
|
||||
const { orderedAccountId, poNumber } = body;
|
||||
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
@ -479,4 +589,127 @@ export class AccountsController {
|
||||
async getDealers(): Promise<GetDealersResponse> {
|
||||
return await this.accountService.getDealers();
|
||||
}
|
||||
|
||||
@Post('/issue/cancel')
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: CancelIssueResponse,
|
||||
description: '成功時のレスポンス',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.BAD_REQUEST,
|
||||
description:
|
||||
'対象注文のステータスが発行済以外/発行日から15日以降/ライセンスをユーザに割り当てている',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.UNAUTHORIZED,
|
||||
description: '認証エラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
description: '想定外のサーバーエラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiOperation({
|
||||
operationId: 'cancelIssue',
|
||||
description: 'ライセンス発行をキャンセルします',
|
||||
})
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(
|
||||
RoleGuard.requireds({
|
||||
roles: [ADMIN_ROLES.ADMIN],
|
||||
tiers: [TIERS.TIER1, TIERS.TIER2],
|
||||
}),
|
||||
)
|
||||
async cancelIssue(
|
||||
@Req() req: Request,
|
||||
@Body() body: CancelIssueRequest,
|
||||
): Promise<CancelIssueResponse> {
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const context = makeContext(payload.userId);
|
||||
|
||||
// TODO: 発行キャンセル処理(仮)。API実装のタスク(2498)で本実装
|
||||
// await this.accountService.cancelIssue(
|
||||
// context,
|
||||
// body.poNumber,
|
||||
// body.orderedAccountId,
|
||||
// );
|
||||
return {};
|
||||
}
|
||||
|
||||
@Get('/worktypes')
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: GetWorktypesResponse,
|
||||
description: '成功時のレスポンス',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.UNAUTHORIZED,
|
||||
description: '認証エラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
description: '想定外のサーバーエラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiOperation({ operationId: 'getWorktypes' })
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
|
||||
async getWorktypes(@Req() req: Request): Promise<GetWorktypesResponse> {
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
const worktypes = await this.accountService.getWorktypes(context, userId);
|
||||
|
||||
return worktypes;
|
||||
}
|
||||
|
||||
@Post('/worktypes')
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: CreateWorktypeResponse,
|
||||
description: '成功時のレスポンス',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.BAD_REQUEST,
|
||||
description: 'WorktypeIDが重複 / WorktypeIDが空 / WorktypeIDが20件登録済み',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.UNAUTHORIZED,
|
||||
description: '認証エラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
description: '想定外のサーバーエラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiOperation({ operationId: 'createWorktype' })
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
|
||||
async createWorktype(
|
||||
@Req() req: Request,
|
||||
@Body() body: CreateWorktypesRequest,
|
||||
): Promise<CreateWorktypeResponse> {
|
||||
const { worktypeId, description } = body;
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
console.log(context.trackingId);
|
||||
console.log(worktypeId);
|
||||
console.log(description);
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import { AccountsService } from './accounts.service';
|
||||
import { AdB2cModule } from '../../gateways/adb2c/adb2c.module';
|
||||
import { UserGroupsRepositoryModule } from '../../repositories/user_groups/user_groups.repository.module';
|
||||
import { BlobstorageModule } from '../../gateways/blobstorage/blobstorage.module';
|
||||
import { WorktypesRepositoryModule } from '../../repositories/worktypes/worktypes.repository.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -15,6 +16,7 @@ import { BlobstorageModule } from '../../gateways/blobstorage/blobstorage.module
|
||||
UsersRepositoryModule,
|
||||
LicensesRepositoryModule,
|
||||
UserGroupsRepositoryModule,
|
||||
WorktypesRepositoryModule,
|
||||
SendGridModule,
|
||||
AdB2cModule,
|
||||
BlobstorageModule,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -21,6 +21,8 @@ import {
|
||||
GetDealersResponse,
|
||||
Dealer,
|
||||
GetMyAccountResponse,
|
||||
GetTypistGroupResponse,
|
||||
GetWorktypesResponse,
|
||||
} from './types/types';
|
||||
import {
|
||||
DateWithZeroTime,
|
||||
@ -40,6 +42,11 @@ import {
|
||||
OrderNotFoundError,
|
||||
} from '../../repositories/licenses/errors/types';
|
||||
import { BlobstorageService } from '../../gateways/blobstorage/blobstorage.service';
|
||||
import {
|
||||
TypistGroupNotExistError,
|
||||
TypistIdInvalidError,
|
||||
} from '../../repositories/user_groups/errors/types';
|
||||
import { WorktypesRepositoryService } from '../../repositories/worktypes/worktypes.repository.service';
|
||||
|
||||
@Injectable()
|
||||
export class AccountsService {
|
||||
@ -48,6 +55,7 @@ export class AccountsService {
|
||||
private readonly licensesRepository: LicensesRepositoryService,
|
||||
private readonly usersRepository: UsersRepositoryService,
|
||||
private readonly userGroupsRepository: UserGroupsRepositoryService,
|
||||
private readonly worktypesRepository: WorktypesRepositoryService,
|
||||
private readonly adB2cService: AdB2cService,
|
||||
private readonly sendgridService: SendGridService,
|
||||
private readonly blobStorageService: BlobstorageService,
|
||||
@ -106,8 +114,8 @@ export class AccountsService {
|
||||
};
|
||||
return licenseSummaryResponse;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.log('get licenseSummary failed');
|
||||
this.logger.error(`error=${e}`);
|
||||
this.logger.error('get licenseSummary failed');
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
@ -415,6 +423,61 @@ export class AccountsService {
|
||||
this.logger.log(`[OUT] ${this.getTypistGroups.name}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* IDを指定してタイピストグループを取得する
|
||||
* @param context
|
||||
* @param externalId
|
||||
* @param typistGroupId
|
||||
* @returns typist group
|
||||
*/
|
||||
async getTypistGroup(
|
||||
context: Context,
|
||||
externalId: string,
|
||||
typistGroupId: number,
|
||||
): Promise<GetTypistGroupResponse> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.getTypistGroup.name} | params: { externalId: ${externalId}, typistGroupId: ${typistGroupId} };`,
|
||||
);
|
||||
|
||||
try {
|
||||
const { account_id } = await this.usersRepository.findUserByExternalId(
|
||||
externalId,
|
||||
);
|
||||
const userGroup = await this.userGroupsRepository.getTypistGroup(
|
||||
account_id,
|
||||
typistGroupId,
|
||||
);
|
||||
|
||||
return {
|
||||
typistGroupName: userGroup.name,
|
||||
typistIds: userGroup.userGroupMembers.map((x) => x.user_id),
|
||||
};
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
if (e instanceof Error) {
|
||||
switch (e.constructor) {
|
||||
case TypistGroupNotExistError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010908'),
|
||||
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.getTypistGroup.name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets typists
|
||||
@ -870,4 +933,162 @@ export class AccountsService {
|
||||
this.logger.log(`[OUT] ${this.getDealers.name}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* タイピストグループを作成する
|
||||
* @param context
|
||||
* @param externalId
|
||||
* @param typistGroupName
|
||||
* @param typistIds
|
||||
* @returns createTypistGroupResponse
|
||||
**/
|
||||
async createTypistGroup(
|
||||
context: Context,
|
||||
externalId: string,
|
||||
typistGroupName: string,
|
||||
typistIds: number[],
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.createTypistGroup.name} | params: { ` +
|
||||
`externalId: ${externalId}, ` +
|
||||
`typistGroupName: ${typistGroupName}, ` +
|
||||
`typistIds: ${typistIds} };`,
|
||||
);
|
||||
try {
|
||||
// 外部IDをもとにユーザー情報を取得する
|
||||
const { account_id } = await this.usersRepository.findUserByExternalId(
|
||||
externalId,
|
||||
);
|
||||
// API実行ユーザーのアカウントIDでタイピストグループを作成し、タイピストグループとtypistIdsのユーザーを紐付ける
|
||||
await this.userGroupsRepository.createTypistGroup(
|
||||
typistGroupName,
|
||||
typistIds,
|
||||
account_id,
|
||||
);
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
if (e instanceof Error) {
|
||||
switch (e.constructor) {
|
||||
case TypistIdInvalidError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010204'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
default:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* タイピストグループを更新する
|
||||
* @param context
|
||||
* @param externalId
|
||||
* @param typistGroupId
|
||||
* @param typistGroupName
|
||||
* @param typistIds
|
||||
* @returns typist group
|
||||
*/
|
||||
async updateTypistGroup(
|
||||
context: Context,
|
||||
externalId: string,
|
||||
typistGroupId: number,
|
||||
typistGroupName: string,
|
||||
typistIds: number[],
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.updateTypistGroup.name} | params: { typistGroupId: ${typistGroupId}, typistGroupName: ${typistGroupName}, typistIds: ${typistIds} };`,
|
||||
);
|
||||
try {
|
||||
// 外部IDをもとにユーザー情報を取得する
|
||||
const { account_id } = await this.usersRepository.findUserByExternalId(
|
||||
externalId,
|
||||
);
|
||||
|
||||
// タイピストグループと所属するタイピストを更新する
|
||||
await this.userGroupsRepository.updateTypistGroup(
|
||||
account_id,
|
||||
typistGroupId,
|
||||
typistGroupName,
|
||||
typistIds,
|
||||
);
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
if (e instanceof Error) {
|
||||
switch (e.constructor) {
|
||||
// タイピストIDが存在しない場合は400エラーを返す
|
||||
case TypistIdInvalidError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010204'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
// タイピストグループIDが存在しない場合は400エラーを返す
|
||||
case TypistGroupNotExistError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010908'),
|
||||
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.updateTypistGroup.name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ワークタイプ一覧を取得します
|
||||
* @param context
|
||||
* @param externalId
|
||||
* @returns worktypes
|
||||
*/
|
||||
async getWorktypes(
|
||||
context: Context,
|
||||
externalId: string,
|
||||
): Promise<GetWorktypesResponse> {
|
||||
this.logger.log(`[IN] [${context.trackingId}] ${this.getWorktypes.name}`);
|
||||
try {
|
||||
// 外部IDをもとにユーザー情報を取得する
|
||||
const { account_id: accountId } =
|
||||
await this.usersRepository.findUserByExternalId(externalId);
|
||||
|
||||
// ワークタイプ一覧を取得する
|
||||
const worktypes = await this.worktypesRepository.getWorktypes(accountId);
|
||||
|
||||
return {
|
||||
worktypes: worktypes.map((x) => ({
|
||||
id: x.id,
|
||||
worktypeId: x.custom_worktype_id,
|
||||
description: x.description ?? undefined,
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
this.logger.error(e);
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.trackingId}] ${this.getWorktypes.name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,6 +16,12 @@ import { AdB2cUser } from '../../../gateways/adb2c/types/types';
|
||||
import { LicensesRepositoryService } from '../../../repositories/licenses/licenses.repository.service';
|
||||
import { Context } from '../../../common/log';
|
||||
import { BlobstorageService } from '../../../gateways/blobstorage/blobstorage.service';
|
||||
import { Worktype } from '../../../repositories/worktypes/entity/worktype.entity';
|
||||
import { WorktypesRepositoryService } from '../../../repositories/worktypes/worktypes.repository.service';
|
||||
|
||||
export type WorktypesRepositoryMockValue = {
|
||||
getWorktypes: Worktype[] | Error;
|
||||
};
|
||||
|
||||
export type LicensesRepositoryMockValue = {
|
||||
getLicenseOrderHistoryInfo:
|
||||
@ -79,6 +85,7 @@ export const makeAccountsServiceMock = async (
|
||||
sendGridMockValue: SendGridMockValue,
|
||||
blobStorageMockValue: BlobStorageServiceMockValue,
|
||||
licensesRepositoryMockValue: LicensesRepositoryMockValue,
|
||||
worktypesRepositoryMockValue: WorktypesRepositoryMockValue,
|
||||
): Promise<AccountsService> => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [AccountsService],
|
||||
@ -107,6 +114,8 @@ export const makeAccountsServiceMock = async (
|
||||
return makeBlobStorageServiceMock(blobStorageMockValue);
|
||||
case LicensesRepositoryService:
|
||||
return makeLicensesRepositoryMock(licensesRepositoryMockValue);
|
||||
case WorktypesRepositoryService:
|
||||
return makeWorktypesRepositoryMock(worktypesRepositoryMockValue);
|
||||
}
|
||||
})
|
||||
.compile();
|
||||
@ -114,6 +123,19 @@ export const makeAccountsServiceMock = async (
|
||||
return module.get<AccountsService>(AccountsService);
|
||||
};
|
||||
|
||||
export const makeWorktypesRepositoryMock = (
|
||||
value: WorktypesRepositoryMockValue,
|
||||
) => {
|
||||
const { getWorktypes } = value;
|
||||
|
||||
return {
|
||||
getWorktypes:
|
||||
getWorktypes instanceof Error
|
||||
? jest.fn<Promise<void>, []>().mockRejectedValue(getWorktypes)
|
||||
: jest.fn<Promise<Worktype[]>, []>().mockResolvedValue(getWorktypes),
|
||||
};
|
||||
};
|
||||
|
||||
export const makeAccountsRepositoryMock = (
|
||||
value: AccountsRepositoryMockValue,
|
||||
) => {
|
||||
@ -295,6 +317,13 @@ export const makeBlobStorageServiceMock = (
|
||||
};
|
||||
|
||||
// 個別のテストケースに対応してそれぞれのMockを用意するのは無駄が多いのでテストケース内で個別の値を設定する
|
||||
export const makeDefaultWorktypesRepositoryMockValue =
|
||||
(): WorktypesRepositoryMockValue => {
|
||||
return {
|
||||
getWorktypes: [],
|
||||
};
|
||||
};
|
||||
|
||||
export const makeDefaultAccountsRepositoryMockValue =
|
||||
(): AccountsRepositoryMockValue => {
|
||||
let licenseSummaryInfo = new LicenseSummaryInfo();
|
||||
|
||||
@ -1,167 +1,12 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { User } from '../../../repositories/users/entity/user.entity';
|
||||
import { Account } from '../../../repositories/accounts/entity/account.entity';
|
||||
import {
|
||||
License,
|
||||
LicenseOrder,
|
||||
} from '../../../repositories/licenses/entity/license.entity';
|
||||
import { SortCriteria } from '../../../repositories/sort_criteria/entity/sort_criteria.entity';
|
||||
|
||||
// TODO: [PBI 2379] 他のUtilityからコピペしてきたもの。後日整理される前提。
|
||||
export const createAccountAndAdminUser = async (
|
||||
datasource: DataSource,
|
||||
adminExternalId: string,
|
||||
): Promise<{
|
||||
accountId: number;
|
||||
adminId: number;
|
||||
role: string;
|
||||
tier: number;
|
||||
}> => {
|
||||
const { identifiers: account_idf } = await datasource
|
||||
.getRepository(Account)
|
||||
.insert({
|
||||
tier: 1,
|
||||
country: 'JP',
|
||||
delegation_permission: false,
|
||||
locked: false,
|
||||
company_name: 'test inc.',
|
||||
verified: true,
|
||||
deleted_at: '',
|
||||
created_by: 'test_runner',
|
||||
created_at: new Date(),
|
||||
updated_by: 'updater',
|
||||
updated_at: new Date(),
|
||||
});
|
||||
const account = account_idf.pop() as Account;
|
||||
|
||||
const { identifiers: user_idf } = await datasource
|
||||
.getRepository(User)
|
||||
.insert({
|
||||
account_id: account.id,
|
||||
external_id: adminExternalId,
|
||||
role: 'admin none',
|
||||
accepted_terms_version: '1.0',
|
||||
email_verified: true,
|
||||
auto_renew: true,
|
||||
license_alert: true,
|
||||
notification: true,
|
||||
encryption: true,
|
||||
encryption_password: 'password',
|
||||
prompt: true,
|
||||
created_by: 'test_runner',
|
||||
created_at: new Date(),
|
||||
updated_by: 'updater',
|
||||
updated_at: new Date(),
|
||||
});
|
||||
const user = user_idf.pop() as User;
|
||||
|
||||
// Accountの管理者を設定する
|
||||
await datasource.getRepository(Account).update(
|
||||
{ id: user.account_id },
|
||||
{
|
||||
primary_admin_user_id: user.id,
|
||||
},
|
||||
);
|
||||
|
||||
const accountResult = await getAccount(datasource, account.id);
|
||||
const userResult = await getUser(datasource, user.id);
|
||||
|
||||
return {
|
||||
accountId: account.id,
|
||||
adminId: user.id,
|
||||
role: userResult.role,
|
||||
tier: accountResult.tier,
|
||||
};
|
||||
};
|
||||
|
||||
export const createAccount = async (
|
||||
datasource: DataSource,
|
||||
parentAccountId: number,
|
||||
tier: number,
|
||||
companyName: string,
|
||||
): Promise<{ accountId: number }> => {
|
||||
const { identifiers } = await datasource.getRepository(Account).insert({
|
||||
parent_account_id: parentAccountId,
|
||||
tier: tier,
|
||||
country: 'JP',
|
||||
delegation_permission: false,
|
||||
locked: false,
|
||||
company_name: companyName,
|
||||
verified: true,
|
||||
deleted_at: '',
|
||||
created_by: 'test_runner',
|
||||
created_at: new Date(),
|
||||
updated_by: 'updater',
|
||||
updated_at: new Date(),
|
||||
});
|
||||
const account = identifiers.pop() as Account;
|
||||
return { accountId: account.id };
|
||||
};
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: 指定IDのアカウントを取得する
|
||||
* @param dataSource データソース
|
||||
* @param id アカウントID
|
||||
* @returns 該当アカウント
|
||||
*/
|
||||
export const getAccount = async (dataSource: DataSource, id: number) => {
|
||||
return await dataSource.getRepository(Account).findOne({
|
||||
where: { id: id },
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: すべてのアカウントを取得する
|
||||
* @param dataSource データソース
|
||||
* @returns 該当アカウント一覧
|
||||
*/
|
||||
export const getAccounts = async (
|
||||
dataSource: DataSource,
|
||||
): Promise<Account[]> => {
|
||||
return await dataSource.getRepository(Account).find();
|
||||
};
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: 指定ExternalIdのユーザーを取得する
|
||||
* @param dataSource データソース
|
||||
* @param externalId 外部ID
|
||||
* @returns 該当ユーザー
|
||||
*/
|
||||
export const getUserFromExternalID = async (
|
||||
dataSource: DataSource,
|
||||
externalId: string,
|
||||
) => {
|
||||
return await dataSource.getRepository(User).findOne({
|
||||
where: { external_id: externalId },
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: 指定Idのユーザーを取得する
|
||||
* @param dataSource データソース
|
||||
* @param externalId 外部ID
|
||||
* @returns 該当ユーザー
|
||||
*/
|
||||
export const getUser = async (
|
||||
datasource: DataSource,
|
||||
id: number,
|
||||
): Promise<User> => {
|
||||
const user = await datasource.getRepository(User).findOne({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
});
|
||||
return user;
|
||||
};
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: すべてのユーザーを取得する
|
||||
* @param dataSource データソース
|
||||
* @returns 該当ユーザー一覧
|
||||
*/
|
||||
export const getUsers = async (dataSource: DataSource): Promise<User[]> => {
|
||||
return await dataSource.getRepository(User).find();
|
||||
};
|
||||
import { UserGroup } from '../../../repositories/user_groups/entity/user_group.entity';
|
||||
import { UserGroupMember } from '../../../repositories/user_groups/entity/user_group_member.entity';
|
||||
import { Worktype } from '../../../repositories/worktypes/entity/worktype.entity';
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: すべてのソート条件を取得する
|
||||
@ -242,30 +87,58 @@ export const createLicenseOrder = async (
|
||||
identifiers.pop() as License;
|
||||
};
|
||||
|
||||
export const createUser = async (
|
||||
// タイピストグループを取得する
|
||||
export const getTypistGroup = async (
|
||||
datasource: DataSource,
|
||||
accountId: number,
|
||||
external_id: string,
|
||||
role: string,
|
||||
author_id?: string | undefined,
|
||||
): Promise<{ userId: number; externalId: string; authorId: string }> => {
|
||||
const { identifiers } = await datasource.getRepository(User).insert({
|
||||
): Promise<UserGroup[]> => {
|
||||
return await datasource.getRepository(UserGroup).find({
|
||||
where: {
|
||||
account_id: accountId,
|
||||
},
|
||||
});
|
||||
};
|
||||
// タイピストグループメンバーを取得する
|
||||
export const getTypistGroupMember = async (
|
||||
datasource: DataSource,
|
||||
userGroupId: number,
|
||||
): Promise<UserGroupMember[]> => {
|
||||
return await datasource.getRepository(UserGroupMember).find({
|
||||
where: {
|
||||
user_group_id: userGroupId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Worktypeを作成する
|
||||
export const createWorktype = async (
|
||||
datasource: DataSource,
|
||||
accountId: number,
|
||||
worktypeId: string,
|
||||
description?: string,
|
||||
): Promise<Worktype> => {
|
||||
const { identifiers } = await datasource.getRepository(Worktype).insert({
|
||||
account_id: accountId,
|
||||
external_id: external_id,
|
||||
role: role,
|
||||
accepted_terms_version: '1.0',
|
||||
author_id: author_id,
|
||||
email_verified: true,
|
||||
auto_renew: true,
|
||||
license_alert: true,
|
||||
notification: true,
|
||||
encryption: false,
|
||||
prompt: false,
|
||||
custom_worktype_id: worktypeId,
|
||||
description: description ?? null,
|
||||
deleted_at: null,
|
||||
created_by: 'test_runner',
|
||||
created_at: new Date(),
|
||||
updated_by: 'updater',
|
||||
updated_at: new Date(),
|
||||
});
|
||||
const user = identifiers.pop() as User;
|
||||
return { userId: user.id, externalId: external_id, authorId: author_id };
|
||||
const worktype = identifiers.pop() as Worktype;
|
||||
return worktype;
|
||||
};
|
||||
|
||||
// Worktypeを取得する
|
||||
export const getWorktypes = async (
|
||||
datasource: DataSource,
|
||||
accountId: number,
|
||||
): Promise<Worktype[]> => {
|
||||
return await datasource.getRepository(Worktype).find({
|
||||
where: {
|
||||
account_id: accountId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -8,8 +8,11 @@ import {
|
||||
Min,
|
||||
ArrayMinSize,
|
||||
MinLength,
|
||||
IsArray,
|
||||
} from 'class-validator';
|
||||
import { IsAdminPasswordvalid } from '../../../common/validators/admin.validator';
|
||||
import { IsUnique } from '../../../common/validators/IsUnique.validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class CreateAccountRequest {
|
||||
@ApiProperty()
|
||||
@ -131,18 +134,58 @@ export class GetTypistGroupsResponse {
|
||||
typistGroups: TypistGroup[];
|
||||
}
|
||||
|
||||
export class GetTypistGroupRequest {
|
||||
@ApiProperty()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
typistGroupId: number;
|
||||
}
|
||||
export class GetTypistGroupResponse {
|
||||
@ApiProperty()
|
||||
typistGroupName: string;
|
||||
@ApiProperty({ isArray: true, type: 'integer' })
|
||||
typistIds: number[];
|
||||
}
|
||||
|
||||
export class CreateTypistGroupRequest {
|
||||
@ApiProperty({ minLength: 1, maxLength: 50 })
|
||||
@MinLength(1)
|
||||
@MaxLength(50)
|
||||
typistGroupName: string;
|
||||
@ApiProperty({ minItems: 1 })
|
||||
@ApiProperty({ minItems: 1, isArray: true, type: 'integer' })
|
||||
@ArrayMinSize(1)
|
||||
@IsArray()
|
||||
@IsInt({ each: true })
|
||||
@Min(0, { each: true })
|
||||
@IsUnique()
|
||||
typistIds: number[];
|
||||
}
|
||||
|
||||
export class CreateTypistGroupResponse {}
|
||||
|
||||
export class UpdateTypistGroupRequest {
|
||||
@ApiProperty({ minLength: 1, maxLength: 50 })
|
||||
@MinLength(1)
|
||||
@MaxLength(50)
|
||||
typistGroupName: string;
|
||||
@ApiProperty({ minItems: 1, isArray: true, type: 'integer' })
|
||||
@ArrayMinSize(1)
|
||||
@IsArray()
|
||||
@IsInt({ each: true })
|
||||
@Min(0, { each: true })
|
||||
@IsUnique()
|
||||
typistIds: number[];
|
||||
}
|
||||
export class UpdateTypistGroupRequestParam {
|
||||
@ApiProperty()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
typistGroupId: number;
|
||||
}
|
||||
export class UpdateTypistGroupResponse {}
|
||||
|
||||
export class CreatePartnerAccountRequest {
|
||||
@ApiProperty()
|
||||
companyName: string;
|
||||
@ -278,3 +321,38 @@ export class GetDealersResponse {
|
||||
@ApiProperty({ type: [Dealer] })
|
||||
dealers: Dealer[];
|
||||
}
|
||||
|
||||
export class CancelIssueRequest {
|
||||
@ApiProperty({ description: '注文元アカウントID' })
|
||||
orderedAccountId: number;
|
||||
|
||||
@ApiProperty({ description: 'POナンバー' })
|
||||
@Matches(/^[A-Z0-9]+$/)
|
||||
poNumber: string;
|
||||
}
|
||||
|
||||
export class CancelIssueResponse {}
|
||||
|
||||
export class Worktype {
|
||||
@ApiProperty({ description: 'WorktypeのID' })
|
||||
id: number;
|
||||
@ApiProperty({ description: 'WorktypeID' })
|
||||
worktypeId: string;
|
||||
@ApiProperty({ description: 'Worktypeの説明', required: false })
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export class GetWorktypesResponse {
|
||||
@ApiProperty({ type: [Worktype] })
|
||||
worktypes: Worktype[];
|
||||
}
|
||||
|
||||
export class CreateWorktypesRequest {
|
||||
@ApiProperty({ minLength: 1, description: 'WorktypeID' })
|
||||
@MinLength(1)
|
||||
worktypeId: string;
|
||||
@ApiProperty({ description: 'Worktypeの説明', required: false })
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export class CreateWorktypeResponse {}
|
||||
|
||||
@ -51,7 +51,6 @@ export class AuthController {
|
||||
operationId: 'token',
|
||||
})
|
||||
async token(@Body() body: TokenRequest): Promise<TokenResponse> {
|
||||
console.log(body);
|
||||
const idToken = await this.authService.getVerifiedIdToken(body.idToken);
|
||||
|
||||
const isVerified = await this.authService.isVerifiedUser(idToken);
|
||||
|
||||
@ -7,14 +7,10 @@ import {
|
||||
makeFilesServiceMock,
|
||||
} from './test/files.service.mock';
|
||||
import { DataSource } from 'typeorm';
|
||||
import {
|
||||
createAccount,
|
||||
createTask,
|
||||
createUser,
|
||||
makeTestingModuleWithBlob,
|
||||
} from './test/utility';
|
||||
import { createTask, makeTestingModuleWithBlob } from './test/utility';
|
||||
import { FilesService } from './files.service';
|
||||
import { makeContext } from '../../common/log';
|
||||
import { makeTestSimpleAccount, makeTestUser } from '../../common/test/utility';
|
||||
|
||||
describe('音声ファイルアップロードURL取得', () => {
|
||||
it('アップロードSASトークンが乗っているURLを返却する', async () => {
|
||||
@ -306,14 +302,17 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('ダウンロードSASトークンが乗っているURLを取得できる', async () => {
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId, userId, authorId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'author-user-external-id',
|
||||
'author',
|
||||
'AUTHOR_ID',
|
||||
);
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const {
|
||||
external_id: externalId,
|
||||
id: userId,
|
||||
author_id: authorId,
|
||||
} = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'author-user-external-id',
|
||||
role: 'author',
|
||||
author_id: 'AUTHOR_ID',
|
||||
});
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/${userId}`;
|
||||
|
||||
const { audioFileId } = await createTask(
|
||||
@ -343,20 +342,18 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId, userId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'typist-user-external-id',
|
||||
'typist',
|
||||
);
|
||||
const { userId: authorUserId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'author-user-external-id',
|
||||
'author',
|
||||
'AUTHOR_ID',
|
||||
);
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId, id: userId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: 'typist',
|
||||
});
|
||||
const { id: authorUserId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'author-user-external-id',
|
||||
role: 'author',
|
||||
author_id: 'AUTHOR_ID',
|
||||
});
|
||||
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/${authorUserId}`;
|
||||
|
||||
@ -388,26 +385,23 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('Typistの場合、自身が担当するタスクでない場合エラー', async () => {
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'typist-user-external-id',
|
||||
'typist',
|
||||
);
|
||||
const { userId: otherId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'other-typist-user-external-id',
|
||||
'typist',
|
||||
);
|
||||
const { userId: authorUserId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'author-user-external-id',
|
||||
'author',
|
||||
'AUTHOR_ID',
|
||||
);
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: 'typist',
|
||||
});
|
||||
const { id: otherId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'other-typist-user-external-id',
|
||||
role: 'typist',
|
||||
});
|
||||
const { id: authorUserId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'author-user-external-id',
|
||||
role: 'author',
|
||||
author_id: 'AUTHOR_ID',
|
||||
});
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/${authorUserId}`;
|
||||
|
||||
const { audioFileId } = await createTask(
|
||||
@ -438,14 +432,13 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('Authorの場合、自身が登録したタスクでない場合エラー', async () => {
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId, userId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'author-user-external-id',
|
||||
'author',
|
||||
'AUTHOR_ID',
|
||||
);
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId, id: userId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'author-user-external-id',
|
||||
role: 'author',
|
||||
author_id: 'AUTHOR_ID',
|
||||
});
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/${userId}`;
|
||||
|
||||
const { audioFileId } = await createTask(
|
||||
@ -477,14 +470,13 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('Taskが存在しない場合はエラーとなる', async () => {
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'author-user-external-id',
|
||||
'author',
|
||||
'AUTHOR_ID',
|
||||
);
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'author-user-external-id',
|
||||
role: 'author',
|
||||
author_id: 'AUTHOR_ID',
|
||||
});
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
|
||||
@ -503,14 +495,17 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('blobストレージにファイルが存在しない場合はエラーとなる', async () => {
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId, userId, authorId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'author-user-external-id',
|
||||
'author',
|
||||
'AUTHOR_ID',
|
||||
);
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const {
|
||||
external_id: externalId,
|
||||
id: userId,
|
||||
author_id: authorId,
|
||||
} = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'author-user-external-id',
|
||||
role: 'author',
|
||||
author_id: 'AUTHOR_ID',
|
||||
});
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/${userId}`;
|
||||
|
||||
const { audioFileId } = await createTask(
|
||||
@ -561,13 +556,15 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('ダウンロードSASトークンが乗っているURLを取得できる', async () => {
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId, authorId } = await createUser(
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId, author_id: authorId } = await makeTestUser(
|
||||
source,
|
||||
accountId,
|
||||
'author-user-external-id',
|
||||
'author',
|
||||
'AUTHOR_ID',
|
||||
{
|
||||
account_id: accountId,
|
||||
external_id: 'author-user-external-id',
|
||||
role: 'author',
|
||||
author_id: 'AUTHOR_ID',
|
||||
},
|
||||
);
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;
|
||||
|
||||
@ -598,14 +595,13 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId, userId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'typist-user-external-id',
|
||||
'typist',
|
||||
undefined,
|
||||
);
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId, id: userId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: 'typist',
|
||||
author_id: undefined,
|
||||
});
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;
|
||||
|
||||
const { audioFileId } = await createTask(
|
||||
@ -636,21 +632,19 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('Typistの場合、自身が担当するタスクでない場合エラー', async () => {
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'typist-user-external-id',
|
||||
'typist',
|
||||
undefined,
|
||||
);
|
||||
const { userId: otherId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'other-typist-user-external-id',
|
||||
'typist',
|
||||
undefined,
|
||||
);
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: 'typist',
|
||||
author_id: undefined,
|
||||
});
|
||||
const { id: otherId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'other-typist-user-external-id',
|
||||
role: 'typist',
|
||||
author_id: undefined,
|
||||
});
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;
|
||||
|
||||
const { audioFileId } = await createTask(
|
||||
@ -681,14 +675,13 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('Authorの場合、自身が登録したタスクでない場合エラー', async () => {
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'author-user-external-id',
|
||||
'author',
|
||||
'AUTHOR_ID',
|
||||
);
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'author-user-external-id',
|
||||
role: 'author',
|
||||
author_id: 'AUTHOR_ID',
|
||||
});
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;
|
||||
|
||||
const { audioFileId } = await createTask(
|
||||
@ -720,14 +713,13 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('Taskが存在しない場合はエラーとなる', async () => {
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'author-user-external-id',
|
||||
'author',
|
||||
'AUTHOR_ID',
|
||||
);
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'author-user-external-id',
|
||||
role: 'author',
|
||||
author_id: 'AUTHOR_ID',
|
||||
});
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
|
||||
@ -746,13 +738,15 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('blobストレージにファイルが存在しない場合はエラーとなる', async () => {
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId, authorId } = await createUser(
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId, author_id: authorId } = await makeTestUser(
|
||||
source,
|
||||
accountId,
|
||||
'author-user-external-id',
|
||||
'author',
|
||||
'AUTHOR_ID',
|
||||
{
|
||||
account_id: accountId,
|
||||
external_id: 'author-user-external-id',
|
||||
role: 'author',
|
||||
author_id: 'AUTHOR_ID',
|
||||
},
|
||||
);
|
||||
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;
|
||||
|
||||
|
||||
@ -36,58 +36,8 @@ import {
|
||||
BlobstorageServiceMockValue,
|
||||
makeBlobstorageServiceMock,
|
||||
} from './files.service.mock';
|
||||
import { User } from '../../../repositories/users/entity/user.entity';
|
||||
import { Account } from '../../../repositories/accounts/entity/account.entity';
|
||||
import { TemplateFile } from '../../../repositories/template_files/entity/template_file.entity';
|
||||
|
||||
export const createAccount = async (
|
||||
datasource: DataSource,
|
||||
): Promise<{ accountId: number }> => {
|
||||
const { identifiers } = await datasource.getRepository(Account).insert({
|
||||
tier: 5,
|
||||
country: 'US',
|
||||
delegation_permission: false,
|
||||
locked: false,
|
||||
company_name: 'test inc.',
|
||||
verified: true,
|
||||
deleted_at: '',
|
||||
created_by: 'test_runner',
|
||||
created_at: new Date(),
|
||||
updated_by: 'updater',
|
||||
updated_at: new Date(),
|
||||
});
|
||||
const account = identifiers.pop() as Account;
|
||||
return { accountId: account.id };
|
||||
};
|
||||
|
||||
export const createUser = async (
|
||||
datasource: DataSource,
|
||||
accountId: number,
|
||||
external_id: string,
|
||||
role: string,
|
||||
author_id?: string | undefined,
|
||||
): Promise<{ userId: number; externalId: string; authorId: string }> => {
|
||||
const { identifiers } = await datasource.getRepository(User).insert({
|
||||
account_id: accountId,
|
||||
external_id: external_id,
|
||||
role: role,
|
||||
accepted_terms_version: '1.0',
|
||||
author_id: author_id,
|
||||
email_verified: true,
|
||||
auto_renew: true,
|
||||
license_alert: true,
|
||||
notification: true,
|
||||
encryption: false,
|
||||
prompt: false,
|
||||
created_by: 'test_runner',
|
||||
created_at: new Date(),
|
||||
updated_by: 'updater',
|
||||
updated_at: new Date(),
|
||||
});
|
||||
const user = identifiers.pop() as User;
|
||||
return { userId: user.id, externalId: external_id, authorId: author_id };
|
||||
};
|
||||
|
||||
export const createTask = async (
|
||||
datasource: DataSource,
|
||||
account_id: number,
|
||||
|
||||
@ -23,7 +23,8 @@ import {
|
||||
ActivateCardLicensesResponse,
|
||||
ActivateCardLicensesRequest,
|
||||
GetAllocatableLicensesResponse,
|
||||
GetAllocatableLicensesRequest,
|
||||
CancelOrderRequest,
|
||||
CancelOrderResponse,
|
||||
} from './types/types';
|
||||
import { Request } from 'express';
|
||||
import { retrieveAuthorizationToken } from '../../common/http/helper';
|
||||
@ -72,10 +73,6 @@ export class LicensesController {
|
||||
@Req() req: Request,
|
||||
@Body() body: CreateOrdersRequest,
|
||||
): Promise<CreateOrdersResponse> {
|
||||
console.log(req.header('Authorization'));
|
||||
console.log(body);
|
||||
|
||||
// AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
|
||||
@ -114,9 +111,6 @@ export class LicensesController {
|
||||
@Req() req: Request,
|
||||
@Body() body: IssueCardLicensesRequest,
|
||||
): Promise<IssueCardLicensesResponse> {
|
||||
console.log(req.header('Authorization'));
|
||||
console.log(body);
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
|
||||
@ -160,9 +154,6 @@ export class LicensesController {
|
||||
@Req() req: Request,
|
||||
@Body() body: ActivateCardLicensesRequest,
|
||||
): Promise<ActivateCardLicensesResponse> {
|
||||
console.log(req.header('Authorization'));
|
||||
console.log(body);
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
|
||||
@ -216,4 +207,54 @@ export class LicensesController {
|
||||
|
||||
return allocatableLicenses;
|
||||
}
|
||||
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: CancelOrderResponse,
|
||||
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: 'cancelOrder',
|
||||
description: 'ライセンス注文をキャンセルします',
|
||||
})
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(
|
||||
RoleGuard.requireds({
|
||||
roles: [ADMIN_ROLES.ADMIN],
|
||||
tiers: [TIERS.TIER2, TIERS.TIER3, TIERS.TIER4, TIERS.TIER5],
|
||||
}),
|
||||
)
|
||||
@Post('/orders/cancel')
|
||||
async cancelOrder(
|
||||
@Req() req: Request,
|
||||
@Body() body: CancelOrderRequest,
|
||||
): Promise<CancelOrderResponse> {
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const context = makeContext(payload.userId);
|
||||
|
||||
await this.licensesService.cancelOrder(
|
||||
context,
|
||||
payload.userId,
|
||||
body.poNumber,
|
||||
);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,8 +23,6 @@ import { LicensesService } from './licenses.service';
|
||||
import { makeTestingModule } from '../../common/test/modules';
|
||||
import { DataSource } from 'typeorm';
|
||||
import {
|
||||
createAccount,
|
||||
createUser,
|
||||
createCardLicense,
|
||||
createLicense,
|
||||
createCardLicenseIssue,
|
||||
@ -33,11 +31,17 @@ import {
|
||||
selectCardLicense,
|
||||
selectLicense,
|
||||
selectLicenseAllocationHistory,
|
||||
createOrder,
|
||||
selectOrderLicense,
|
||||
} from './test/utility';
|
||||
import { UsersService } from '../users/users.service';
|
||||
import { makeContext } from '../../common/log';
|
||||
import { IsNotIn } from 'class-validator';
|
||||
import { LICENSE_ALLOCATED_STATUS, LICENSE_TYPE } from '../../constants';
|
||||
import {
|
||||
makeHierarchicalAccounts,
|
||||
makeTestSimpleAccount,
|
||||
makeTestUser,
|
||||
} from '../../common/test/utility';
|
||||
|
||||
describe('LicensesService', () => {
|
||||
it('ライセンス注文が完了する', async () => {
|
||||
@ -303,16 +307,16 @@ describe('DBテスト', () => {
|
||||
it('カードライセンス発行が完了する(発行数が合っているか確認)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'userId',
|
||||
'admin',
|
||||
);
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'userId',
|
||||
role: 'admin',
|
||||
author_id: undefined,
|
||||
});
|
||||
|
||||
const service = module.get<LicensesService>(LicensesService);
|
||||
const issueCount = 1000;
|
||||
const issueCount = 500;
|
||||
await service.issueCardLicenseKeys(externalId, issueCount);
|
||||
const dbSelectResult = await selectCardLicensesCount(source);
|
||||
expect(dbSelectResult.count).toEqual(issueCount);
|
||||
@ -321,13 +325,13 @@ describe('DBテスト', () => {
|
||||
it('カードライセンス取り込みが完了する', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'userId',
|
||||
'admin',
|
||||
);
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'userId',
|
||||
role: 'admin',
|
||||
author_id: undefined,
|
||||
});
|
||||
|
||||
const cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
|
||||
const defaultAccountId = 150;
|
||||
@ -364,13 +368,13 @@ describe('DBテスト', () => {
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
const now = new Date();
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'userId',
|
||||
'admin',
|
||||
);
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'userId',
|
||||
role: 'admin',
|
||||
author_id: undefined,
|
||||
});
|
||||
|
||||
// ライセンスを作成する
|
||||
// 1件目
|
||||
@ -499,8 +503,13 @@ describe('ライセンス割り当て', () => {
|
||||
it('未割当のライセンスに対して、ライセンス割り当てが完了する', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
const { accountId } = await createAccount(source);
|
||||
const { userId } = await createUser(source, accountId, 'userId', 'admin');
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'userId',
|
||||
role: 'admin',
|
||||
author_id: undefined,
|
||||
});
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
@ -544,8 +553,13 @@ describe('ライセンス割り当て', () => {
|
||||
it('再割り当て可能なライセンスに対して、ライセンス割り当てが完了する', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
const { accountId } = await createAccount(source);
|
||||
const { userId } = await createUser(source, accountId, 'userId', 'admin');
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'userId',
|
||||
role: 'admin',
|
||||
author_id: undefined,
|
||||
});
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + 30);
|
||||
await createLicense(
|
||||
@ -585,8 +599,13 @@ describe('ライセンス割り当て', () => {
|
||||
it('未割当のライセンスに対して、別のライセンスが割り当てられているユーザーの割り当てが完了する', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
const { accountId } = await createAccount(source);
|
||||
const { userId } = await createUser(source, accountId, 'userId', 'admin');
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'userId',
|
||||
role: 'admin',
|
||||
author_id: undefined,
|
||||
});
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + 30);
|
||||
await createLicense(
|
||||
@ -661,8 +680,13 @@ describe('ライセンス割り当て', () => {
|
||||
it('割り当て時にライセンス履歴テーブルへの登録が完了する(元がNORMALのとき)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
const { accountId } = await createAccount(source);
|
||||
const { userId } = await createUser(source, accountId, 'userId', 'admin');
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'userId',
|
||||
role: 'admin',
|
||||
author_id: undefined,
|
||||
});
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + 30);
|
||||
await createLicense(
|
||||
@ -701,8 +725,13 @@ describe('ライセンス割り当て', () => {
|
||||
it('割り当て時にライセンス履歴テーブルへの登録が完了する(元がCARDのとき)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
const { accountId } = await createAccount(source);
|
||||
const { userId } = await createUser(source, accountId, 'userId', 'admin');
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'userId',
|
||||
role: 'admin',
|
||||
author_id: undefined,
|
||||
});
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + 30);
|
||||
await createLicense(
|
||||
@ -741,8 +770,13 @@ describe('ライセンス割り当て', () => {
|
||||
it('割り当て時にライセンス履歴テーブルへの登録が完了する(元がTRIALのとき)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
const { accountId } = await createAccount(source);
|
||||
const { userId } = await createUser(source, accountId, 'userId', 'admin');
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'userId',
|
||||
role: 'admin',
|
||||
author_id: undefined,
|
||||
});
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + 30);
|
||||
await createLicense(
|
||||
@ -781,8 +815,13 @@ describe('ライセンス割り当て', () => {
|
||||
it('有効期限が切れているライセンスを割り当てようとした場合、エラーになる', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
const { accountId } = await createAccount(source);
|
||||
const { userId } = await createUser(source, accountId, 'userId', 'admin');
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'userId',
|
||||
role: 'admin',
|
||||
author_id: undefined,
|
||||
});
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - 30);
|
||||
await createLicense(
|
||||
@ -807,8 +846,13 @@ describe('ライセンス割り当て', () => {
|
||||
it('割り当て不可なライセンスを割り当てようとした場合、エラーになる', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
const { accountId } = await createAccount(source);
|
||||
const { userId } = await createUser(source, accountId, 'userId', 'admin');
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'userId',
|
||||
role: 'admin',
|
||||
author_id: undefined,
|
||||
});
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + 30);
|
||||
await createLicense(
|
||||
@ -844,3 +888,237 @@ 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('ライセンスの割り当て解除が完了する', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'userId',
|
||||
role: 'admin',
|
||||
author_id: undefined,
|
||||
});
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + 30);
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
date,
|
||||
accountId,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
userId,
|
||||
);
|
||||
await createLicenseAllocationHistory(source, 1, userId, 1, 'NONE');
|
||||
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
await service.deallocateLicense(makeContext('trackingId'), userId);
|
||||
|
||||
// 割り当て解除したライセンスの状態確認
|
||||
const deallocatedLicense = await selectLicense(source, 1);
|
||||
expect(deallocatedLicense.license.allocated_user_id).toBe(null);
|
||||
expect(deallocatedLicense.license.status).toBe(
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE,
|
||||
);
|
||||
expect(deallocatedLicense.license.expiry_date).toEqual(date);
|
||||
|
||||
// ライセンス履歴テーブルの状態確認
|
||||
const licenseAllocationHistory = await selectLicenseAllocationHistory(
|
||||
source,
|
||||
userId,
|
||||
1,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe(
|
||||
userId,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe(
|
||||
1,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe(
|
||||
false,
|
||||
);
|
||||
expect(
|
||||
licenseAllocationHistory.licenseAllocationHistory.switch_from_type,
|
||||
).toBe('NONE');
|
||||
});
|
||||
|
||||
it('ライセンスが既に割り当て解除されていた場合、エラーとなる', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'userId',
|
||||
role: 'admin',
|
||||
author_id: undefined,
|
||||
});
|
||||
await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'userId2',
|
||||
role: 'admin',
|
||||
author_id: undefined,
|
||||
});
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + 30);
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
date,
|
||||
accountId,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
2,
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
2,
|
||||
date,
|
||||
accountId,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE,
|
||||
userId,
|
||||
);
|
||||
await createLicenseAllocationHistory(source, 1, userId, 1, 'NONE');
|
||||
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
await expect(
|
||||
service.deallocateLicense(makeContext('trackingId'), userId),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010807'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
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('ライセンス注文のキャンセルが完了する', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts(
|
||||
source,
|
||||
);
|
||||
const poNumber = 'CANCEL_TEST';
|
||||
await createOrder(
|
||||
source,
|
||||
poNumber,
|
||||
tier2Accounts[0].account.id,
|
||||
tier2Accounts[0].account.parent_account_id,
|
||||
10,
|
||||
'Issue Requesting',
|
||||
);
|
||||
// キャンセル済みの同名poNumoberが存在しても正常に動作することの確認用order
|
||||
await createOrder(
|
||||
source,
|
||||
poNumber,
|
||||
tier2Accounts[0].account.id,
|
||||
tier2Accounts[0].account.parent_account_id,
|
||||
10,
|
||||
'Order Canceled',
|
||||
);
|
||||
|
||||
const service = module.get<LicensesService>(LicensesService);
|
||||
await service.cancelOrder(
|
||||
makeContext('trackingId'),
|
||||
tier2Accounts[0].users[0].external_id,
|
||||
poNumber,
|
||||
);
|
||||
|
||||
// 割り当て解除したライセンスの状態確認
|
||||
const orderRecord = await selectOrderLicense(
|
||||
source,
|
||||
tier2Accounts[0].account.id,
|
||||
poNumber,
|
||||
);
|
||||
expect(orderRecord.orderLicense.canceled_at).toBeDefined();
|
||||
expect(orderRecord.orderLicense.status).toBe('Order Canceled');
|
||||
});
|
||||
|
||||
it('ライセンスが既に発行済みの場合、エラーとなる', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts(
|
||||
source,
|
||||
);
|
||||
const poNumber = 'CANCEL_TEST';
|
||||
await createOrder(
|
||||
source,
|
||||
poNumber,
|
||||
tier2Accounts[0].account.id,
|
||||
tier2Accounts[0].account.parent_account_id,
|
||||
10,
|
||||
'Issued',
|
||||
);
|
||||
|
||||
const service = module.get<LicensesService>(LicensesService);
|
||||
await expect(
|
||||
service.cancelOrder(
|
||||
makeContext('trackingId'),
|
||||
tier2Accounts[0].users[0].external_id,
|
||||
poNumber,
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010808'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
|
||||
it('ライセンスが既にキャンセル済みの場合、エラーとなる', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
|
||||
const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts(
|
||||
source,
|
||||
);
|
||||
const poNumber = 'CANCEL_TEST';
|
||||
await createOrder(
|
||||
source,
|
||||
poNumber,
|
||||
tier2Accounts[0].account.id,
|
||||
tier2Accounts[0].account.parent_account_id,
|
||||
10,
|
||||
'Order Canceled',
|
||||
);
|
||||
|
||||
const service = module.get<LicensesService>(LicensesService);
|
||||
await expect(
|
||||
service.cancelOrder(
|
||||
makeContext('trackingId'),
|
||||
tier2Accounts[0].users[0].external_id,
|
||||
poNumber,
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010808'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
PoNumberAlreadyExistError,
|
||||
LicenseNotExistError,
|
||||
LicenseKeyAlreadyActivatedError,
|
||||
CancelOrderFailedError,
|
||||
} from '../../repositories/licenses/errors/types';
|
||||
import { LicensesRepositoryService } from '../../repositories/licenses/licenses.repository.service';
|
||||
import { UserNotFoundError } from '../../repositories/users/errors/types';
|
||||
@ -255,4 +256,54 @@ export class LicensesService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ライセンス注文をキャンセルする
|
||||
* @param context
|
||||
* @param externalId
|
||||
* @param poNumber
|
||||
*/
|
||||
async cancelOrder(
|
||||
context: Context,
|
||||
externalId: string,
|
||||
poNumber: string,
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.cancelOrder.name} | params: { ` +
|
||||
`externalId: ${externalId}, ` +
|
||||
`poNumber: ${poNumber}, };`,
|
||||
);
|
||||
let myAccountId: number;
|
||||
|
||||
try {
|
||||
// ユーザIDからアカウントIDを取得する
|
||||
myAccountId = (
|
||||
await this.usersRepository.findUserByExternalId(externalId)
|
||||
).account_id;
|
||||
// 注文キャンセル処理
|
||||
await this.licensesRepository.cancelOrder(myAccountId, poNumber);
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
switch (e.constructor) {
|
||||
case UserNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010204'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
case CancelOrderFailedError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010808'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
default:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
this.logger.log(`[OUT] [${context.trackingId}] ${this.cancelOrder.name}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,61 +1,12 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { User } from '../../../repositories/users/entity/user.entity';
|
||||
import { Account } from '../../../repositories/accounts/entity/account.entity';
|
||||
import {
|
||||
License,
|
||||
CardLicense,
|
||||
CardLicenseIssue,
|
||||
LicenseAllocationHistory,
|
||||
LicenseOrder,
|
||||
} from '../../../repositories/licenses/entity/license.entity';
|
||||
|
||||
export const createAccount = async (
|
||||
datasource: DataSource,
|
||||
): Promise<{ accountId: number }> => {
|
||||
const { identifiers } = await datasource.getRepository(Account).insert({
|
||||
tier: 1,
|
||||
country: 'JP',
|
||||
delegation_permission: false,
|
||||
locked: false,
|
||||
company_name: 'test inc.',
|
||||
verified: true,
|
||||
deleted_at: '',
|
||||
created_by: 'test_runner',
|
||||
created_at: new Date(),
|
||||
updated_by: 'updater',
|
||||
updated_at: new Date(),
|
||||
});
|
||||
const account = identifiers.pop() as Account;
|
||||
return { accountId: account.id };
|
||||
};
|
||||
|
||||
export const createUser = async (
|
||||
datasource: DataSource,
|
||||
accountId: number,
|
||||
external_id: string,
|
||||
role: string,
|
||||
author_id?: string | undefined,
|
||||
): Promise<{ userId: number; externalId: string }> => {
|
||||
const { identifiers } = await datasource.getRepository(User).insert({
|
||||
account_id: accountId,
|
||||
external_id: external_id,
|
||||
role: role,
|
||||
accepted_terms_version: '1.0',
|
||||
author_id: author_id,
|
||||
email_verified: true,
|
||||
auto_renew: true,
|
||||
license_alert: true,
|
||||
notification: true,
|
||||
encryption: false,
|
||||
prompt: false,
|
||||
created_by: 'test_runner',
|
||||
created_at: new Date(),
|
||||
updated_by: 'updater',
|
||||
updated_at: new Date(),
|
||||
});
|
||||
const user = identifiers.pop() as User;
|
||||
return { userId: user.id, externalId: external_id };
|
||||
};
|
||||
|
||||
export const createLicense = async (
|
||||
datasource: DataSource,
|
||||
licenseId: number,
|
||||
@ -144,6 +95,31 @@ export const createLicenseAllocationHistory = async (
|
||||
identifiers.pop() as LicenseAllocationHistory;
|
||||
};
|
||||
|
||||
export const createOrder = async (
|
||||
datasource: DataSource,
|
||||
poNumber: string,
|
||||
fromId: number,
|
||||
toId: number,
|
||||
quantity: number,
|
||||
status: string,
|
||||
): Promise<void> => {
|
||||
const { identifiers } = await datasource.getRepository(LicenseOrder).insert({
|
||||
po_number: poNumber,
|
||||
from_account_id: fromId,
|
||||
to_account_id: toId,
|
||||
ordered_at: new Date(),
|
||||
issued_at: null,
|
||||
quantity: quantity,
|
||||
status: status,
|
||||
canceled_at: null,
|
||||
created_by: null,
|
||||
created_at: new Date(),
|
||||
updated_by: null,
|
||||
updated_at: new Date(),
|
||||
});
|
||||
identifiers.pop() as LicenseOrder;
|
||||
};
|
||||
|
||||
export const selectCardLicensesCount = async (
|
||||
datasource: DataSource,
|
||||
): Promise<{ count: number }> => {
|
||||
@ -193,3 +169,17 @@ export const selectLicenseAllocationHistory = async (
|
||||
});
|
||||
return { licenseAllocationHistory };
|
||||
};
|
||||
|
||||
export const selectOrderLicense = async (
|
||||
datasource: DataSource,
|
||||
accountId: number,
|
||||
poNumber: string,
|
||||
): Promise<{ orderLicense: LicenseOrder }> => {
|
||||
const orderLicense = await datasource.getRepository(LicenseOrder).findOne({
|
||||
where: {
|
||||
from_account_id: accountId,
|
||||
po_number: poNumber,
|
||||
},
|
||||
});
|
||||
return { orderLicense };
|
||||
};
|
||||
|
||||
@ -55,6 +55,14 @@ export class GetAllocatableLicensesResponse {
|
||||
allocatableLicenses: AllocatableLicenseInfo[];
|
||||
}
|
||||
|
||||
export class CancelOrderRequest {
|
||||
@ApiProperty()
|
||||
@Matches(/^[A-Z0-9]+$/)
|
||||
poNumber: string;
|
||||
}
|
||||
|
||||
export class CancelOrderResponse {}
|
||||
|
||||
// ライセンス算出用に、その日の始まりの時刻(0:00:00.000)の日付を取得する
|
||||
export class DateWithZeroTime extends Date {
|
||||
constructor(...args: any[]) {
|
||||
|
||||
@ -137,9 +137,6 @@ export class TasksController {
|
||||
@Headers() headers,
|
||||
@Query() body: AudioNextRequest,
|
||||
): Promise<AudioNextResponse> {
|
||||
const { endedFileId } = body;
|
||||
console.log(endedFileId);
|
||||
|
||||
return { nextFileId: 1234 };
|
||||
}
|
||||
|
||||
@ -403,9 +400,6 @@ export class TasksController {
|
||||
@Headers() headers,
|
||||
@Param() params: ChangeStatusRequest,
|
||||
): Promise<ChangeStatusResponse> {
|
||||
const { audioFileId } = params;
|
||||
console.log(audioFileId);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -445,9 +439,6 @@ export class TasksController {
|
||||
@Headers() headers,
|
||||
@Param() params: ChangeStatusRequest,
|
||||
): Promise<ChangeStatusResponse> {
|
||||
const { audioFileId } = params;
|
||||
console.log(audioFileId);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,6 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { User } from '../../../repositories/users/entity/user.entity';
|
||||
import { Account } from '../../../repositories/accounts/entity/account.entity';
|
||||
import { Task } from '../../../repositories/tasks/entity/task.entity';
|
||||
import { AudioFile } from '../../../repositories/audio_files/entity/audio_file.entity';
|
||||
import { CheckoutPermission } from '../../../repositories/checkout_permissions/entity/checkout_permission.entity';
|
||||
@ -102,54 +100,6 @@ export const makeTaskTestingModule = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const createAccount = async (
|
||||
datasource: DataSource,
|
||||
): Promise<{ accountId: number }> => {
|
||||
const { identifiers } = await datasource.getRepository(Account).insert({
|
||||
tier: 1,
|
||||
country: 'JP',
|
||||
delegation_permission: false,
|
||||
locked: false,
|
||||
company_name: 'test inc.',
|
||||
verified: true,
|
||||
deleted_at: '',
|
||||
created_by: 'test_runner',
|
||||
created_at: new Date(),
|
||||
updated_by: 'updater',
|
||||
updated_at: new Date(),
|
||||
});
|
||||
const account = identifiers.pop() as Account;
|
||||
return { accountId: account.id };
|
||||
};
|
||||
|
||||
export const createUser = async (
|
||||
datasource: DataSource,
|
||||
accountId: number,
|
||||
external_id: string,
|
||||
role: string,
|
||||
author_id?: string | undefined,
|
||||
): Promise<{ userId: number; externalId: string }> => {
|
||||
const { identifiers } = await datasource.getRepository(User).insert({
|
||||
account_id: accountId,
|
||||
external_id: external_id,
|
||||
role: role,
|
||||
accepted_terms_version: '1.0',
|
||||
author_id: author_id,
|
||||
email_verified: true,
|
||||
auto_renew: true,
|
||||
license_alert: true,
|
||||
notification: true,
|
||||
encryption: false,
|
||||
prompt: false,
|
||||
created_by: 'test_runner',
|
||||
created_at: new Date(),
|
||||
updated_by: 'updater',
|
||||
updated_at: new Date(),
|
||||
});
|
||||
const user = identifiers.pop() as User;
|
||||
return { userId: user.id, externalId: external_id };
|
||||
};
|
||||
|
||||
export const createTask = async (
|
||||
datasource: DataSource,
|
||||
account_id: number,
|
||||
|
||||
@ -29,8 +29,6 @@ import { NotificationhubService } from '../../../gateways/notificationhub/notifi
|
||||
import { FilesService } from '../../../features/files/files.service';
|
||||
import { LicensesService } from '../../../features/licenses/licenses.service';
|
||||
import { TasksService } from '../../../features/tasks/tasks.service';
|
||||
import { User } from '../../../repositories/users/entity/user.entity';
|
||||
import { Account } from '../../../repositories/accounts/entity/account.entity';
|
||||
import { UserGroup } from '../../../repositories/user_groups/entity/user_group.entity';
|
||||
import { UserGroupMember } from '../../../repositories/user_groups/entity/user_group_member.entity';
|
||||
import { License } from '../../../repositories/licenses/entity/license.entity';
|
||||
@ -38,150 +36,6 @@ import { AdB2cMockValue, makeAdB2cServiceMock } from './users.service.mock';
|
||||
import { AdB2cService } from '../../../gateways/adb2c/adb2c.service';
|
||||
import { LICENSE_ALLOCATED_STATUS, LICENSE_TYPE } from '../../../constants';
|
||||
|
||||
export const createAccountAndAdminUser = async (
|
||||
datasource: DataSource,
|
||||
adminExternalId: string,
|
||||
): Promise<{
|
||||
accountId: number;
|
||||
adminId: number;
|
||||
role: string;
|
||||
tier: number;
|
||||
}> => {
|
||||
const { identifiers: account_idf } = await datasource
|
||||
.getRepository(Account)
|
||||
.insert({
|
||||
tier: 1,
|
||||
country: 'JP',
|
||||
delegation_permission: false,
|
||||
locked: false,
|
||||
company_name: 'test inc.',
|
||||
verified: true,
|
||||
deleted_at: '',
|
||||
created_by: 'test_runner',
|
||||
created_at: new Date(),
|
||||
updated_by: 'updater',
|
||||
updated_at: new Date(),
|
||||
});
|
||||
const account = account_idf.pop() as Account;
|
||||
|
||||
const { identifiers: user_idf } = await datasource
|
||||
.getRepository(User)
|
||||
.insert({
|
||||
account_id: account.id,
|
||||
external_id: adminExternalId,
|
||||
role: 'admin none',
|
||||
accepted_terms_version: '1.0',
|
||||
email_verified: true,
|
||||
auto_renew: true,
|
||||
license_alert: true,
|
||||
notification: true,
|
||||
encryption: true,
|
||||
encryption_password: 'password',
|
||||
prompt: true,
|
||||
created_by: 'test_runner',
|
||||
created_at: new Date(),
|
||||
updated_by: 'updater',
|
||||
updated_at: new Date(),
|
||||
});
|
||||
const user = user_idf.pop() as User;
|
||||
|
||||
// Accountの管理者を設定する
|
||||
await datasource.getRepository(Account).update(
|
||||
{ id: user.account_id },
|
||||
{
|
||||
primary_admin_user_id: user.id,
|
||||
},
|
||||
);
|
||||
|
||||
const accountResult = await getAccount(datasource, account.id);
|
||||
const userResult = await getUser(datasource, user.id);
|
||||
|
||||
return {
|
||||
accountId: account.id,
|
||||
adminId: user.id,
|
||||
role: userResult.role,
|
||||
tier: accountResult.tier,
|
||||
};
|
||||
};
|
||||
|
||||
export const createAccount = async (
|
||||
datasource: DataSource,
|
||||
): Promise<{ accountId: number }> => {
|
||||
const { identifiers } = await datasource.getRepository(Account).insert({
|
||||
tier: 1,
|
||||
country: 'JP',
|
||||
delegation_permission: false,
|
||||
locked: false,
|
||||
company_name: 'test inc.',
|
||||
verified: true,
|
||||
deleted_at: '',
|
||||
created_by: 'test_runner',
|
||||
created_at: new Date(),
|
||||
updated_by: 'updater',
|
||||
updated_at: new Date(),
|
||||
});
|
||||
const account = identifiers.pop() as Account;
|
||||
return { accountId: account.id };
|
||||
};
|
||||
|
||||
export const createUser = async (
|
||||
datasource: DataSource,
|
||||
accountId: number,
|
||||
external_id: string,
|
||||
role: string,
|
||||
author_id?: string | undefined,
|
||||
auto_renew?: boolean,
|
||||
encryption?: boolean | undefined,
|
||||
encryption_password?: string | undefined,
|
||||
prompt?: boolean | undefined,
|
||||
email_verified?: boolean | undefined,
|
||||
): Promise<{ userId: number; externalId: string }> => {
|
||||
const { identifiers } = await datasource.getRepository(User).insert({
|
||||
account_id: accountId,
|
||||
external_id: external_id,
|
||||
role: role,
|
||||
accepted_terms_version: '1.0',
|
||||
author_id: author_id,
|
||||
email_verified: email_verified ?? true,
|
||||
auto_renew: auto_renew,
|
||||
license_alert: true,
|
||||
notification: true,
|
||||
encryption: encryption ?? false,
|
||||
encryption_password: encryption_password,
|
||||
prompt: prompt ?? false,
|
||||
created_by: 'test_runner',
|
||||
created_at: new Date(),
|
||||
updated_by: 'updater',
|
||||
updated_at: new Date(),
|
||||
});
|
||||
const user = identifiers.pop() as User;
|
||||
return { userId: user.id, externalId: external_id };
|
||||
};
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: 指定IDのアカウントを取得する
|
||||
* @param dataSource データソース
|
||||
* @param id アカウントID
|
||||
* @returns 該当アカウント
|
||||
*/
|
||||
export const getAccount = async (dataSource: DataSource, id: number) => {
|
||||
return await dataSource.getRepository(Account).findOne({
|
||||
where: { id: id },
|
||||
});
|
||||
};
|
||||
|
||||
export const getUser = async (
|
||||
datasource: DataSource,
|
||||
id: number,
|
||||
): Promise<User> => {
|
||||
const user = await datasource.getRepository(User).findOne({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
});
|
||||
return user;
|
||||
};
|
||||
|
||||
export const getLicenses = async (
|
||||
datasource: DataSource,
|
||||
account_id: number,
|
||||
@ -194,33 +48,6 @@ export const getLicenses = async (
|
||||
return licenses;
|
||||
};
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: 指定外部IDを持つユーザーを取得する
|
||||
* @param dataSource データソース
|
||||
* @param externalId 外部ID
|
||||
* @returns 該当ユーザー
|
||||
*/
|
||||
export const getUserByExternalId = async (
|
||||
datasource: DataSource,
|
||||
externalId: string,
|
||||
): Promise<User> => {
|
||||
const user = await datasource.getRepository(User).findOne({
|
||||
where: {
|
||||
external_id: externalId,
|
||||
},
|
||||
});
|
||||
return user;
|
||||
};
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: すべてのユーザーを取得する
|
||||
* @param dataSource データソース
|
||||
* @returns 該当ユーザー一覧
|
||||
*/
|
||||
export const getUsers = async (dataSource: DataSource): Promise<User[]> => {
|
||||
return await dataSource.getRepository(User).find();
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param datasource
|
||||
|
||||
@ -461,13 +461,12 @@ export class UsersController {
|
||||
@Body() body: DeallocateLicenseRequest,
|
||||
@Req() req: Request,
|
||||
): Promise<DeallocateLicenseResponse> {
|
||||
//API実装時に詳細をかいていく
|
||||
//const accessToken = retrieveAuthorizationToken(req);
|
||||
//const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
|
||||
//const context = makeContext(userId);
|
||||
const context = makeContext(userId);
|
||||
|
||||
//await this.usersService.deallocateLicense(context, body.userId);
|
||||
await this.usersService.deallocateLicense(context, body.userId);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -41,6 +41,7 @@ import { DateWithZeroTime } from '../licenses/types/types';
|
||||
import { Context } from '../../common/log';
|
||||
import { UserRoles } from '../../common/types/role';
|
||||
import {
|
||||
LicenseAlreadyDeallocatedError,
|
||||
LicenseExpiredError,
|
||||
LicenseUnavailableError,
|
||||
} from '../../repositories/licenses/errors/types';
|
||||
@ -886,7 +887,7 @@ export class UsersService {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.allocateLicense.name} | params: { ` +
|
||||
`userId: ${userId}, ` +
|
||||
`newLicenseId: ${newLicenseId}, `,
|
||||
`newLicenseId: ${newLicenseId}, };`,
|
||||
);
|
||||
|
||||
try {
|
||||
@ -918,4 +919,40 @@ export class UsersService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ユーザーに割り当てられているライセンスを解除します
|
||||
* @param context
|
||||
* @param userId
|
||||
*/
|
||||
async deallocateLicense(context: Context, userId: number): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.deallocateLicense.name} | params: { ` +
|
||||
`userId: ${userId}, };`,
|
||||
);
|
||||
|
||||
try {
|
||||
await this.licensesRepository.deallocateLicense(userId);
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
if (e instanceof Error) {
|
||||
switch (e.constructor) {
|
||||
case LicenseAlreadyDeallocatedError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010807'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
default:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.trackingId}] ${this.deallocateLicense.name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ import {
|
||||
} from '../../common/types/sort/util';
|
||||
import {
|
||||
LICENSE_ALLOCATED_STATUS,
|
||||
LICENSE_STATUS_ISSUE_REQUESTING,
|
||||
LICENSE_ISSUE_STATUS,
|
||||
TIERS,
|
||||
} from '../../constants';
|
||||
import {
|
||||
@ -330,7 +330,7 @@ export class AccountsRepositoryService {
|
||||
const numberOfRequesting = await licenseOrder.count({
|
||||
where: {
|
||||
from_account_id: id,
|
||||
status: LICENSE_STATUS_ISSUE_REQUESTING,
|
||||
status: LICENSE_ISSUE_STATUS.ISSUE_REQUESTING,
|
||||
},
|
||||
});
|
||||
|
||||
@ -340,7 +340,7 @@ export class AccountsRepositoryService {
|
||||
.select('SUM(license_orders.quantity)', 'sum')
|
||||
.where('license_orders.from_account_id = :id', { id })
|
||||
.andWhere('license_orders.status = :status', {
|
||||
status: LICENSE_STATUS_ISSUE_REQUESTING,
|
||||
status: LICENSE_ISSUE_STATUS.ISSUE_REQUESTING,
|
||||
})
|
||||
.getRawOne();
|
||||
const issueRequesting = parseInt(result.sum, 10) || 0;
|
||||
@ -415,7 +415,7 @@ export class AccountsRepositoryService {
|
||||
.select('SUM(license_orders.quantity)', 'sum')
|
||||
.where('license_orders.to_account_id = :id', { id })
|
||||
.andWhere('license_orders.status = :status', {
|
||||
status: LICENSE_STATUS_ISSUE_REQUESTING,
|
||||
status: LICENSE_ISSUE_STATUS.ISSUE_REQUESTING,
|
||||
})
|
||||
.getRawOne();
|
||||
const issuedRequested = parseInt(issuedRequestedSqlResult.sum, 10) || 0;
|
||||
@ -426,7 +426,7 @@ export class AccountsRepositoryService {
|
||||
.select('SUM(license_orders.quantity)', 'sum')
|
||||
.where('license_orders.from_account_id = :id', { id })
|
||||
.andWhere('license_orders.status = :status', {
|
||||
status: LICENSE_STATUS_ISSUE_REQUESTING,
|
||||
status: LICENSE_ISSUE_STATUS.ISSUE_REQUESTING,
|
||||
})
|
||||
.getRawOne();
|
||||
const issuedRequesting = parseInt(issuedRequestingSqlResult.sum, 10) || 0;
|
||||
|
||||
@ -97,26 +97,6 @@ export class License {
|
||||
@JoinColumn({ name: 'allocated_user_id' })
|
||||
user?: User;
|
||||
}
|
||||
@Entity({ name: 'licenses_history' })
|
||||
export class LicenseHistory {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
user_id: number;
|
||||
|
||||
@Column()
|
||||
license_id: number;
|
||||
|
||||
@Column()
|
||||
allocated: boolean;
|
||||
|
||||
@Column()
|
||||
executed_at: Date;
|
||||
|
||||
@Column()
|
||||
exchange_type: string;
|
||||
}
|
||||
|
||||
@Entity({ name: 'card_license_issue' })
|
||||
export class CardLicenseIssue {
|
||||
|
||||
@ -18,3 +18,9 @@ export class LicensesShortageError extends Error {}
|
||||
export class LicenseExpiredError extends Error {}
|
||||
// ライセンス割り当て不可エラー
|
||||
export class LicenseUnavailableError extends Error {}
|
||||
|
||||
// ライセンス割り当て解除済みエラー
|
||||
export class LicenseAlreadyDeallocatedError extends Error {}
|
||||
|
||||
// 注文キャンセル失敗エラー
|
||||
export class CancelOrderFailedError extends Error {}
|
||||
|
||||
@ -10,8 +10,7 @@ import {
|
||||
import {
|
||||
CARD_LICENSE_LENGTH,
|
||||
LICENSE_ALLOCATED_STATUS,
|
||||
LICENSE_STATUS_ISSUE_REQUESTING,
|
||||
LICENSE_STATUS_ISSUED,
|
||||
LICENSE_ISSUE_STATUS,
|
||||
LICENSE_TYPE,
|
||||
SWITCH_FROM_TYPE,
|
||||
TIERS,
|
||||
@ -25,6 +24,8 @@ import {
|
||||
OrderNotFoundError,
|
||||
LicenseExpiredError,
|
||||
LicenseUnavailableError,
|
||||
LicenseAlreadyDeallocatedError,
|
||||
CancelOrderFailedError,
|
||||
} from './errors/types';
|
||||
import {
|
||||
AllocatableLicenseInfo,
|
||||
@ -48,7 +49,7 @@ export class LicensesRepositoryService {
|
||||
licenseOrder.from_account_id = fromAccountId;
|
||||
licenseOrder.to_account_id = toAccountId;
|
||||
licenseOrder.quantity = quantity;
|
||||
licenseOrder.status = LICENSE_STATUS_ISSUE_REQUESTING;
|
||||
licenseOrder.status = LICENSE_ISSUE_STATUS.ISSUE_REQUESTING;
|
||||
|
||||
// ライセンス注文テーブルに登録する
|
||||
const createdEntity = await this.dataSource.transaction(
|
||||
@ -61,12 +62,12 @@ export class LicensesRepositoryService {
|
||||
{
|
||||
po_number: poNumber,
|
||||
from_account_id: fromAccountId,
|
||||
status: LICENSE_STATUS_ISSUED,
|
||||
status: LICENSE_ISSUE_STATUS.ISSUED,
|
||||
},
|
||||
{
|
||||
po_number: poNumber,
|
||||
from_account_id: fromAccountId,
|
||||
status: LICENSE_STATUS_ISSUE_REQUESTING,
|
||||
status: LICENSE_ISSUE_STATUS.ISSUE_REQUESTING,
|
||||
},
|
||||
],
|
||||
});
|
||||
@ -102,8 +103,7 @@ export class LicensesRepositoryService {
|
||||
const cardLicenseIssueRepo =
|
||||
entityManager.getRepository(CardLicenseIssue);
|
||||
|
||||
const licenses = [];
|
||||
//TODO タスク 2409: カードライセンスのレコード作成がbulkinsertになっていない
|
||||
const licenses: License[] = [];
|
||||
// ライセンステーブルを作成する(BULK INSERT)
|
||||
for (let i = 0; i < count; i++) {
|
||||
const license = new License();
|
||||
@ -112,7 +112,12 @@ export class LicensesRepositoryService {
|
||||
license.type = LICENSE_TYPE.CARD;
|
||||
licenses.push(license);
|
||||
}
|
||||
const savedLicenses = await licensesRepo.save(licenses);
|
||||
const savedLicenses = await licensesRepo
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(License)
|
||||
.values(licenses)
|
||||
.execute();
|
||||
|
||||
// カードライセンス発行テーブルを作成する
|
||||
const cardLicenseIssue = new CardLicenseIssue();
|
||||
@ -159,17 +164,21 @@ export class LicensesRepositoryService {
|
||||
isDuplicateKeysExist = false;
|
||||
}
|
||||
|
||||
const cardLicenses = [];
|
||||
//TODO タスク 2409: カードライセンスのレコード作成がbulkinsertになっていない
|
||||
const cardLicenses: CardLicense[] = [];
|
||||
// カードライセンステーブルを作成する(BULK INSERT)
|
||||
for (let i = 0; i < count; i++) {
|
||||
const cardLicense = new CardLicense();
|
||||
cardLicense.license_id = savedLicenses[i].id; // Licenseテーブルの自動採番されたIDを挿入
|
||||
cardLicense.license_id = savedLicenses.generatedMaps[i].id; // Licenseテーブルの自動採番されたIDを挿入
|
||||
cardLicense.issue_id = savedCardLicensesIssue.id; // CardLicenseIssueテーブルの自動採番されたIDを挿入
|
||||
cardLicense.card_license_key = licenseKeys[i];
|
||||
cardLicenses.push(cardLicense);
|
||||
}
|
||||
await cardLicenseRepo.save(cardLicenses);
|
||||
await cardLicenseRepo
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(CardLicense)
|
||||
.values(cardLicenses)
|
||||
.execute();
|
||||
});
|
||||
return licenseKeys;
|
||||
}
|
||||
@ -342,7 +351,7 @@ export class LicensesRepositoryService {
|
||||
throw new OrderNotFoundError(`No order found for PONumber:${poNumber}`);
|
||||
}
|
||||
// 既に発行済みの注文の場合、エラー
|
||||
if (issuingOrder.status !== LICENSE_STATUS_ISSUE_REQUESTING) {
|
||||
if (issuingOrder.status !== LICENSE_ISSUE_STATUS.ISSUE_REQUESTING) {
|
||||
throw new AlreadyIssuedError(
|
||||
`An order for PONumber:${poNumber} has already been issued.`,
|
||||
);
|
||||
@ -362,11 +371,16 @@ export class LicensesRepositoryService {
|
||||
{ id: issuingOrder.id },
|
||||
{
|
||||
issued_at: nowDate,
|
||||
status: LICENSE_STATUS_ISSUED,
|
||||
status: LICENSE_ISSUE_STATUS.ISSUED,
|
||||
},
|
||||
);
|
||||
// ライセンステーブルを登録(注文元)
|
||||
await licenseRepo.save(newLicenses);
|
||||
await licenseRepo
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(License)
|
||||
.values(newLicenses)
|
||||
.execute();
|
||||
|
||||
// 第一階層の場合はストックライセンスの概念が存在しないため、ストックライセンス変更処理は行わない
|
||||
if (tier !== TIERS.TIER1) {
|
||||
@ -410,6 +424,7 @@ export class LicensesRepositoryService {
|
||||
): Promise<AllocatableLicenseInfo[]> {
|
||||
const nowDate = new DateWithZeroTime();
|
||||
const licenseRepo = this.dataSource.getRepository(License);
|
||||
// EntityManagerではorderBy句で、expiry_dateに対して複数条件でソートを使用するため出来ない為、createQueryBuilderを使用する。
|
||||
const queryBuilder = licenseRepo
|
||||
.createQueryBuilder('license')
|
||||
.where('license.account_id = :accountId', { accountId: myAccountId })
|
||||
@ -541,4 +556,77 @@ export class LicensesRepositoryService {
|
||||
await licenseAllocationHistoryRepo.save(allocationHistory);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* ユーザーに割り当てられているライセンスを解除する
|
||||
* @param userId
|
||||
*/
|
||||
async deallocateLicense(userId: number): Promise<void> {
|
||||
await this.dataSource.transaction(async (entityManager) => {
|
||||
const licenseRepo = entityManager.getRepository(License);
|
||||
const licenseAllocationHistoryRepo = entityManager.getRepository(
|
||||
LicenseAllocationHistory,
|
||||
);
|
||||
// 対象ユーザーのライセンス割り当て状態を取得
|
||||
const allocatedLicense = await licenseRepo.findOne({
|
||||
where: {
|
||||
allocated_user_id: userId,
|
||||
status: LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
},
|
||||
});
|
||||
|
||||
// ライセンスが割り当てられていない場合はエラー
|
||||
if (!allocatedLicense) {
|
||||
throw new LicenseAlreadyDeallocatedError(
|
||||
`License is already deallocated. userId: ${userId}`,
|
||||
);
|
||||
}
|
||||
|
||||
// ライセンスの割り当てを解除
|
||||
allocatedLicense.status = LICENSE_ALLOCATED_STATUS.REUSABLE;
|
||||
allocatedLicense.allocated_user_id = null;
|
||||
await licenseRepo.save(allocatedLicense);
|
||||
|
||||
// ライセンス割り当て履歴テーブルへ登録
|
||||
const deallocationHistory = new LicenseAllocationHistory();
|
||||
deallocationHistory.user_id = userId;
|
||||
deallocationHistory.license_id = allocatedLicense.id;
|
||||
deallocationHistory.is_allocated = false;
|
||||
deallocationHistory.executed_at = new Date();
|
||||
deallocationHistory.switch_from_type = SWITCH_FROM_TYPE.NONE;
|
||||
await licenseAllocationHistoryRepo.save(deallocationHistory);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* ライセンス注文をキャンセルする
|
||||
* @param accountId
|
||||
* @param poNumber
|
||||
*/
|
||||
async cancelOrder(accountId: number, poNumber: string): Promise<void> {
|
||||
await this.dataSource.transaction(async (entityManager) => {
|
||||
const orderRepo = entityManager.getRepository(LicenseOrder);
|
||||
|
||||
// キャンセル対象の注文を取得
|
||||
const targetOrder = await orderRepo.findOne({
|
||||
where: {
|
||||
from_account_id: accountId,
|
||||
po_number: poNumber,
|
||||
status: LICENSE_ISSUE_STATUS.ISSUE_REQUESTING,
|
||||
},
|
||||
});
|
||||
|
||||
// キャンセル対象の注文が存在しない場合エラー
|
||||
if (!targetOrder) {
|
||||
throw new CancelOrderFailedError(
|
||||
`Cancel order is failed. accountId: ${accountId}, poNumber: ${poNumber}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 注文キャンセル処理
|
||||
targetOrder.status = LICENSE_ISSUE_STATUS.CANCELED;
|
||||
targetOrder.canceled_at = new Date();
|
||||
await orderRepo.save(targetOrder);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,11 @@
|
||||
import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm';
|
||||
import {
|
||||
Entity,
|
||||
Column,
|
||||
PrimaryGeneratedColumn,
|
||||
OneToMany,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { UserGroupMember } from './user_group_member.entity';
|
||||
|
||||
@Entity({ name: 'user_group' })
|
||||
@ -15,16 +22,16 @@ export class UserGroup {
|
||||
@Column({ nullable: true })
|
||||
deleted_at?: Date;
|
||||
|
||||
@Column()
|
||||
created_by: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
created_by?: string;
|
||||
|
||||
@CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
created_at?: Date;
|
||||
|
||||
@Column()
|
||||
updated_by: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
updated_by?: string;
|
||||
|
||||
@UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
updated_at?: Date;
|
||||
|
||||
@OneToMany(
|
||||
|
||||
@ -5,6 +5,8 @@ import {
|
||||
PrimaryGeneratedColumn,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { UserGroup } from './user_group.entity';
|
||||
|
||||
@ -22,16 +24,16 @@ export class UserGroupMember {
|
||||
@Column({ nullable: true })
|
||||
deleted_at?: Date;
|
||||
|
||||
@Column()
|
||||
created_by: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
created_by?: string;
|
||||
|
||||
@CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
created_at?: Date;
|
||||
|
||||
@Column()
|
||||
updated_by: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
updated_by?: string;
|
||||
|
||||
@UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
updated_at?: Date;
|
||||
|
||||
@ManyToOne(() => User, (user) => user.id)
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
// タイピストグループが存在しないエラー
|
||||
export class TypistGroupNotExistError extends Error {}
|
||||
// typistIdが不正な場合のエラー
|
||||
export class TypistIdInvalidError extends Error {}
|
||||
@ -2,6 +2,9 @@ import { Injectable } from '@nestjs/common';
|
||||
import { DataSource, In, IsNull } from 'typeorm';
|
||||
import { UserGroup } from './entity/user_group.entity';
|
||||
import { UserGroupMember } from './entity/user_group_member.entity';
|
||||
import { User } from '../users/entity/user.entity';
|
||||
import { TypistGroupNotExistError, TypistIdInvalidError } from './errors/types';
|
||||
import { USER_ROLES } from '../../constants';
|
||||
|
||||
@Injectable()
|
||||
export class UserGroupsRepositoryService {
|
||||
@ -43,4 +46,155 @@ export class UserGroupsRepositoryService {
|
||||
return groupMembers;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* タイピストグループと所属するメンバーのIDを取得します
|
||||
* @param accountId
|
||||
* @param typistGroupId
|
||||
* @returns typist group
|
||||
*/
|
||||
async getTypistGroup(
|
||||
accountId: number,
|
||||
typistGroupId: number,
|
||||
): Promise<UserGroup> {
|
||||
return await this.dataSource.transaction(async (entityManager) => {
|
||||
const userGroupRepo = entityManager.getRepository(UserGroup);
|
||||
|
||||
const userGroup = await userGroupRepo.findOne({
|
||||
where: {
|
||||
id: typistGroupId,
|
||||
account_id: accountId,
|
||||
deleted_at: IsNull(),
|
||||
},
|
||||
relations: {
|
||||
userGroupMembers: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!userGroup) {
|
||||
throw new TypistGroupNotExistError(
|
||||
`Typist Group is not exist. typistGroupId: ${typistGroupId}`,
|
||||
);
|
||||
}
|
||||
return userGroup;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定したアカウントIDでタイピストグループを作成し、そのタイピストグループとtypistIdsのユーザーを紐付ける
|
||||
* @param accountId
|
||||
* @param name
|
||||
* @param typistIds
|
||||
* @returns createdTypistGroup
|
||||
*/
|
||||
async createTypistGroup(
|
||||
name: string,
|
||||
typistIds: number[],
|
||||
accountId: number,
|
||||
): Promise<UserGroup> {
|
||||
return await this.dataSource.transaction(async (entityManager) => {
|
||||
const userGroupRepo = entityManager.getRepository(UserGroup);
|
||||
const userGroupMemberRepo = entityManager.getRepository(UserGroupMember);
|
||||
// typistIdsのidを持つユーザーが、account_idのアカウントに所属していて、かつ、roleがtypistであることを確認する
|
||||
const userRepo = entityManager.getRepository(User);
|
||||
const userRecords = await userRepo.find({
|
||||
where: {
|
||||
id: In(typistIds),
|
||||
account_id: accountId,
|
||||
role: USER_ROLES.TYPIST,
|
||||
},
|
||||
});
|
||||
if (userRecords.length !== typistIds.length) {
|
||||
throw new TypistIdInvalidError(
|
||||
`Typist user not exists Error. typistIds:${typistIds}; typistIds(DB):${userRecords.map(
|
||||
(x) => x.id,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
// userGroupをDBに保存する
|
||||
const userGroup = await userGroupRepo.save({
|
||||
account_id: accountId,
|
||||
name,
|
||||
});
|
||||
|
||||
const userGroupMembers = userRecords.map((user) => {
|
||||
return {
|
||||
user_group_id: userGroup.id,
|
||||
user_id: user.id,
|
||||
};
|
||||
});
|
||||
// userGroupMembersをDBに保存する
|
||||
await userGroupMemberRepo.save(userGroupMembers);
|
||||
|
||||
return userGroup;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定したIDのタイピストグループを更新し、そのタイピストグループとtypistIdsのユーザーを紐付ける
|
||||
* @param accountId
|
||||
* @param name
|
||||
* @param typistIds
|
||||
* @returns createdTypistGroup
|
||||
*/
|
||||
async updateTypistGroup(
|
||||
accountId: number,
|
||||
typistGroupId: number,
|
||||
typistGroupName: string,
|
||||
typistIds: number[],
|
||||
): Promise<UserGroup> {
|
||||
return await this.dataSource.transaction(async (entityManager) => {
|
||||
const userGroupRepo = entityManager.getRepository(UserGroup);
|
||||
const userGroupMemberRepo = entityManager.getRepository(UserGroupMember);
|
||||
// typistIdsのidを持つユーザーが、account_idのアカウントに所属していて、かつ、roleがtypistであることを確認する
|
||||
const userRepo = entityManager.getRepository(User);
|
||||
const userRecords = await userRepo.find({
|
||||
where: {
|
||||
id: In(typistIds),
|
||||
account_id: accountId,
|
||||
role: USER_ROLES.TYPIST,
|
||||
},
|
||||
});
|
||||
if (userRecords.length !== typistIds.length) {
|
||||
throw new TypistIdInvalidError(
|
||||
`Typist user not exists Error. typistIds:${typistIds}; typistIds(DB):${userRecords.map(
|
||||
(x) => x.id,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
|
||||
// GroupIdが自アカウント内に存在するか確認する
|
||||
const typistGroup = await userGroupRepo.findOne({
|
||||
where: {
|
||||
id: typistGroupId,
|
||||
account_id: accountId,
|
||||
},
|
||||
});
|
||||
if (!typistGroup) {
|
||||
throw new TypistGroupNotExistError(
|
||||
`TypistGroup not exists Error. accountId: ${accountId}; typistGroupId: ${typistGroupId}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 対象のタイピストグループを更新する
|
||||
// ユーザーグループ名を更新する
|
||||
typistGroup.name = typistGroupName;
|
||||
await userGroupRepo.save(typistGroup);
|
||||
|
||||
// user_group_membersテーブルから対象のタイピストグループのユーザーを削除する
|
||||
await userGroupMemberRepo.delete({
|
||||
user_group_id: typistGroupId,
|
||||
});
|
||||
|
||||
const typistGroupMembers = userRecords.map((typist) => {
|
||||
return {
|
||||
user_group_id: typistGroup.id,
|
||||
user_id: typist.id,
|
||||
};
|
||||
});
|
||||
await userGroupMemberRepo.save(typistGroupMembers);
|
||||
|
||||
return typistGroup;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -317,14 +317,7 @@ export class UsersRepositoryService {
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(License)
|
||||
.values(
|
||||
licenses.map((value) => ({
|
||||
expiry_date: value.expiry_date,
|
||||
account_id: value.account_id,
|
||||
type: value.type,
|
||||
status: value.status,
|
||||
})),
|
||||
)
|
||||
.values(licenses)
|
||||
.execute();
|
||||
});
|
||||
}
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
import { Account } from '../../accounts/entity/account.entity';
|
||||
import {
|
||||
Entity,
|
||||
Column,
|
||||
PrimaryGeneratedColumn,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
@Entity({ name: 'worktypes' })
|
||||
export class Worktype {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
account_id: number;
|
||||
|
||||
@Column()
|
||||
custom_worktype_id: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
description?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
deleted_at?: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
created_by: string;
|
||||
|
||||
@CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
created_at: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
updated_by?: string;
|
||||
|
||||
@UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
updated_at: Date;
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Worktype } from './entity/worktype.entity';
|
||||
import { WorktypesRepositoryService } from './worktypes.repository.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Worktype])],
|
||||
providers: [WorktypesRepositoryService],
|
||||
exports: [WorktypesRepositoryService],
|
||||
})
|
||||
export class WorktypesRepositoryModule {}
|
||||
@ -0,0 +1,22 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { Worktype } from './entity/worktype.entity';
|
||||
|
||||
@Injectable()
|
||||
export class WorktypesRepositoryService {
|
||||
constructor(private dataSource: DataSource) {}
|
||||
|
||||
/**
|
||||
* ワークタイプ一覧を取得する
|
||||
* @param accountId
|
||||
* @returns worktypes
|
||||
*/
|
||||
async getWorktypes(accountId: number): Promise<Worktype[]> {
|
||||
return await this.dataSource.transaction(async (entityManager) => {
|
||||
const repo = entityManager.getRepository(Worktype);
|
||||
|
||||
const worktypes = await repo.find({ where: { account_id: accountId } });
|
||||
return worktypes;
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user