Merge branch 'develop' into main
This commit is contained in:
commit
0e800c6aec
1
.env
1
.env
@ -5,4 +5,5 @@ DB_NAME=omds
|
||||
DB_ROOT_PASS=omdsdbpass
|
||||
DB_USERNAME=omdsdbuser
|
||||
DB_PASSWORD=omdsdbpass
|
||||
REDIS_PASSWORD=omdsredispass
|
||||
NO_COLOR=TRUE
|
||||
@ -1,3 +1,6 @@
|
||||
# Pipeline側でKeyVaultやDocker、AppService等に対する操作権限を持ったServiceConenctionを作成し、
|
||||
# 環境変数 AZURE_SERVICE_CONNECTION の値としてServiceConenction名を設定しておくこと
|
||||
# また、環境変数 STATIC_DICTATION_DEPLOYMENT_TOKEN の値として静的WebAppsのデプロイトークンを設定しておくこと
|
||||
trigger:
|
||||
tags:
|
||||
include:
|
||||
@ -22,36 +25,98 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
displayName: 'タグが付けられたCommitがmainブランチに存在するか確認'
|
||||
- job: backend_build
|
||||
dependsOn: initialize
|
||||
condition: succeeded('initialize')
|
||||
displayName: Dictation App Service Deploy
|
||||
- job: backend_deploy
|
||||
displayName: Backend Deploy
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- job: frontend_build
|
||||
dependsOn: initialize
|
||||
condition: succeeded('initialize')
|
||||
displayName: Dictation Static App Service Deploy
|
||||
- task: AzureRmWebAppDeployment@4
|
||||
inputs:
|
||||
ConnectionType: 'AzureRM'
|
||||
azureSubscription: $(AZURE_SERVICE_CONNECTION)
|
||||
appType: 'webAppContainer'
|
||||
WebAppName: 'app-odms-dictation-prod'
|
||||
ResourceGroupName: 'prod-application-rg'
|
||||
DockerNamespace: 'crodmsregistrymaintenance.azurecr.io'
|
||||
DockerRepository: '$(Build.Repository.Name)/staging/dictation'
|
||||
DockerImageTag: '$(Build.SourceVersion)'
|
||||
- job: frontend_deploy
|
||||
displayName: Deploy Frontend Files
|
||||
variables:
|
||||
storageAccountName: saomdspipeline
|
||||
containerName: staging
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureKeyVault@2
|
||||
displayName: 'Azure Key Vault: kv-odms-secret-prod'
|
||||
inputs:
|
||||
ConnectedServiceName: $(AZURE_SERVICE_CONNECTION)
|
||||
KeyVaultName: kv-odms-secret-prod
|
||||
SecretsFilter: '*'
|
||||
- task: AzureCLI@2
|
||||
inputs:
|
||||
azureSubscription: $(AZURE_SERVICE_CONNECTION)
|
||||
scriptType: 'bash'
|
||||
scriptLocation: 'inlineScript'
|
||||
inlineScript: |
|
||||
az storage blob download \
|
||||
--auth-mode login \
|
||||
--account-name $(storageAccountName) \
|
||||
--container-name $(containerName) \
|
||||
--name $(Build.SourceVersion).zip \
|
||||
--file $(Build.SourcesDirectory)/$(Build.SourceVersion).zip
|
||||
- task: Bash@3
|
||||
displayName: Bash Script
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: unzip $(Build.SourcesDirectory)/$(Build.SourceVersion).zip -d $(Build.SourcesDirectory)/$(Build.SourceVersion)
|
||||
- task: AzureStaticWebApp@0
|
||||
displayName: 'Static Web App: '
|
||||
inputs:
|
||||
workingDirectory: '$(Build.SourcesDirectory)'
|
||||
app_location: '/$(Build.SourceVersion)'
|
||||
config_file_location: /dictation_client
|
||||
skip_app_build: true
|
||||
skip_api_build: true
|
||||
is_static_export: false
|
||||
verbose: false
|
||||
azure_static_web_apps_api_token: $(STATIC_DICTATION_DEPLOYMENT_TOKEN)
|
||||
- job: migration
|
||||
condition: succeeded('initialize')
|
||||
displayName: DB migration
|
||||
dependsOn:
|
||||
- initialize
|
||||
- backend_build
|
||||
- frontend_build
|
||||
- backend_deploy
|
||||
- frontend_deploy
|
||||
pool:
|
||||
name: db-migrate-pipelines
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureKeyVault@2
|
||||
displayName: 'Azure Key Vault: kv-odms-secret-prod'
|
||||
inputs:
|
||||
ConnectedServiceName: $(AZURE_SERVICE_CONNECTION)
|
||||
KeyVaultName: kv-odms-secret-prod
|
||||
- task: CmdLine@2
|
||||
displayName: migration
|
||||
inputs:
|
||||
script: >2
|
||||
# DB接続情報書き換え
|
||||
sed -i -e "s/DB_NAME/$(db-name)/g" ./dictation_server/db/dbconfig.yml
|
||||
sed -i -e "s/DB_PASS/$(db-pass)/g" ./dictation_server/db/dbconfig.yml
|
||||
sed -i -e "s/DB_USERNAME/$(db-user)/g" ./dictation_server/db/dbconfig.yml
|
||||
sed -i -e "s/DB_PORT/$(db-port)/g" ./dictation_server/db/dbconfig.yml
|
||||
sed -i -e "s/DB_HOST/$(db-host)/g" ./dictation_server/db/dbconfig.yml
|
||||
sql-migrate --version
|
||||
cat ./dictation_server/db/dbconfig.yml
|
||||
# migration実行
|
||||
sql-migrate up -config=./dictation_server/db/dbconfig.yml -env=ci
|
||||
@ -1,3 +1,6 @@
|
||||
# Pipeline側でKeyVaultやDocker、AppService等に対する操作権限を持ったServiceConenctionを作成し、
|
||||
# 環境変数 AZURE_SERVICE_CONNECTION の値としてServiceConenction名を設定しておくこと
|
||||
# また、環境変数 STATIC_DICTATION_DEPLOYMENT_TOKEN の値として静的WebAppsのデプロイトークンを設定しておくこと
|
||||
trigger:
|
||||
branches:
|
||||
include:
|
||||
@ -28,33 +31,203 @@ jobs:
|
||||
- job: backend_build
|
||||
dependsOn: initialize
|
||||
condition: succeeded('initialize')
|
||||
displayName: Dictation App Service Deploy
|
||||
displayName: Build And Push Backend Image
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: Npm@1
|
||||
displayName: npm ci
|
||||
inputs:
|
||||
command: ci
|
||||
workingDir: dictation_server
|
||||
verbose: false
|
||||
- task: AzureKeyVault@2
|
||||
displayName: 'Azure Key Vault: kv-odms-secret-stg'
|
||||
inputs:
|
||||
ConnectedServiceName: $(AZURE_SERVICE_CONNECTION)
|
||||
KeyVaultName: kv-odms-secret-stg
|
||||
SecretsFilter: '*'
|
||||
- task: Bash@3
|
||||
displayName: Bash Script (Test)
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
cd dictation_server
|
||||
npm run test
|
||||
env:
|
||||
JWT_PUBLIC_KEY: $(token-public-key)
|
||||
SENDGRID_API_KEY: $(sendgrid-api-key)
|
||||
NOTIFICATION_HUB_NAME: $(notification-hub-name)
|
||||
NOTIFICATION_HUB_CONNECT_STRING: $(notification-hub-connect-string)
|
||||
STORAGE_ACCOUNT_NAME_US: $(storage-account-name-us)
|
||||
STORAGE_ACCOUNT_NAME_AU: $(storage-account-name-au)
|
||||
STORAGE_ACCOUNT_NAME_EU: $(storage-account-name-eu)
|
||||
STORAGE_ACCOUNT_KEY_US: $(storage-account-key-us)
|
||||
STORAGE_ACCOUNT_KEY_AU: $(storage-account-key-au)
|
||||
STORAGE_ACCOUNT_KEY_EU: $(storage-account-key-eu)
|
||||
STORAGE_ACCOUNT_ENDPOINT_US: $(storage-account-endpoint-us)
|
||||
STORAGE_ACCOUNT_ENDPOINT_AU: $(storage-account-endpoint-au)
|
||||
STORAGE_ACCOUNT_ENDPOINT_EU: $(storage-account-endpoint-eu)
|
||||
ADB2C_TENANT_ID: $(adb2c-tenant-id)
|
||||
ADB2C_CLIENT_ID: $(adb2c-client-id)
|
||||
ADB2C_CLIENT_SECRET: $(adb2c-client-secret)
|
||||
- task: Docker@0
|
||||
displayName: build
|
||||
inputs:
|
||||
azureSubscriptionEndpoint: $(AZURE_SERVICE_CONNECTION)
|
||||
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||
dockerFile: DockerfileServerDictation.dockerfile
|
||||
imageName: $(Build.Repository.Name)/staging/dictation:$(Build.SourceVersion)
|
||||
- task: Docker@0
|
||||
displayName: push
|
||||
inputs:
|
||||
azureSubscriptionEndpoint: $(AZURE_SERVICE_CONNECTION)
|
||||
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||
action: Push an image
|
||||
imageName: $(Build.Repository.Name)/staging/dictation:$(Build.SourceVersion)
|
||||
- job: backend_deploy
|
||||
dependsOn: backend_build
|
||||
condition: succeeded('backend_build')
|
||||
displayName: Backend Deploy
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureRmWebAppDeployment@4
|
||||
inputs:
|
||||
ConnectionType: 'AzureRM'
|
||||
azureSubscription: $(AZURE_SERVICE_CONNECTION)
|
||||
appType: 'webAppContainer'
|
||||
WebAppName: 'app-odms-dictation-stg'
|
||||
ResourceGroupName: 'stg-application-rg'
|
||||
DockerNamespace: 'crodmsregistrymaintenance.azurecr.io'
|
||||
DockerRepository: '$(Build.Repository.Name)/staging/dictation'
|
||||
DockerImageTag: '$(Build.SourceVersion)'
|
||||
- job: frontend_build
|
||||
dependsOn: initialize
|
||||
condition: succeeded('initialize')
|
||||
displayName: Dictation Static App Service Deploy
|
||||
displayName: Build Frontend Files
|
||||
variables:
|
||||
storageAccountName: saomdspipeline
|
||||
containerName: staging
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: Npm@1
|
||||
displayName: npm ci
|
||||
inputs:
|
||||
command: ci
|
||||
workingDir: dictation_client
|
||||
verbose: false
|
||||
- task: Bash@3
|
||||
displayName: Bash Script
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: cd dictation_client && npm run build
|
||||
- task: ArchiveFiles@2
|
||||
inputs:
|
||||
rootFolderOrFile: dictation_client/build
|
||||
includeRootFolder: false
|
||||
archiveType: 'zip'
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip'
|
||||
replaceExistingArchive: true
|
||||
- task: AzureCLI@2
|
||||
inputs:
|
||||
azureSubscription: $(AZURE_SERVICE_CONNECTION)
|
||||
scriptType: 'bash'
|
||||
scriptLocation: 'inlineScript'
|
||||
inlineScript: |
|
||||
az storage blob upload \
|
||||
--auth-mode login \
|
||||
--account-name $(storageAccountName) \
|
||||
--container-name $(containerName) \
|
||||
--name $(Build.SourceVersion).zip \
|
||||
--type block \
|
||||
--file $(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip
|
||||
- job: frontend_deploy
|
||||
dependsOn: frontend_build
|
||||
condition: succeeded('frontend_build')
|
||||
displayName: Deploy Frontend Files
|
||||
variables:
|
||||
storageAccountName: saomdspipeline
|
||||
containerName: staging
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureKeyVault@2
|
||||
displayName: 'Azure Key Vault: kv-odms-secret-stg'
|
||||
inputs:
|
||||
ConnectedServiceName: $(AZURE_SERVICE_CONNECTION)
|
||||
KeyVaultName: kv-odms-secret-stg
|
||||
SecretsFilter: '*'
|
||||
- task: AzureCLI@2
|
||||
inputs:
|
||||
azureSubscription: $(AZURE_SERVICE_CONNECTION)
|
||||
scriptType: 'bash'
|
||||
scriptLocation: 'inlineScript'
|
||||
inlineScript: |
|
||||
az storage blob download \
|
||||
--auth-mode login \
|
||||
--account-name $(storageAccountName) \
|
||||
--container-name $(containerName) \
|
||||
--name $(Build.SourceVersion).zip \
|
||||
--file $(Build.SourcesDirectory)/$(Build.SourceVersion).zip
|
||||
- task: Bash@3
|
||||
displayName: Bash Script
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: unzip $(Build.SourcesDirectory)/$(Build.SourceVersion).zip -d $(Build.SourcesDirectory)/$(Build.SourceVersion)
|
||||
- task: AzureStaticWebApp@0
|
||||
displayName: 'Static Web App: '
|
||||
inputs:
|
||||
workingDirectory: '$(Build.SourcesDirectory)'
|
||||
app_location: '/$(Build.SourceVersion)'
|
||||
config_file_location: /dictation_client
|
||||
skip_app_build: true
|
||||
skip_api_build: true
|
||||
is_static_export: false
|
||||
verbose: false
|
||||
azure_static_web_apps_api_token: $(STATIC_DICTATION_DEPLOYMENT_TOKEN)
|
||||
- job: migration
|
||||
condition: succeeded('initialize')
|
||||
displayName: DB migration
|
||||
dependsOn:
|
||||
- initialize
|
||||
- backend_build
|
||||
- frontend_build
|
||||
- backend_deploy
|
||||
- frontend_deploy
|
||||
pool:
|
||||
name: db-migrate-pipelines
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
fetchDepth: 1
|
||||
- task: AzureKeyVault@2
|
||||
displayName: 'Azure Key Vault: kv-odms-secret-stg'
|
||||
inputs:
|
||||
ConnectedServiceName: $(AZURE_SERVICE_CONNECTION)
|
||||
KeyVaultName: kv-odms-secret-stg
|
||||
- task: CmdLine@2
|
||||
displayName: migration
|
||||
inputs:
|
||||
script: >2
|
||||
# DB接続情報書き換え
|
||||
sed -i -e "s/DB_NAME/$(db-name)/g" ./dictation_server/db/dbconfig.yml
|
||||
sed -i -e "s/DB_PASS/$(db-pass)/g" ./dictation_server/db/dbconfig.yml
|
||||
sed -i -e "s/DB_USERNAME/$(db-user)/g" ./dictation_server/db/dbconfig.yml
|
||||
sed -i -e "s/DB_PORT/$(db-port)/g" ./dictation_server/db/dbconfig.yml
|
||||
sed -i -e "s/DB_HOST/$(db-host)/g" ./dictation_server/db/dbconfig.yml
|
||||
sql-migrate --version
|
||||
cat ./dictation_server/db/dbconfig.yml
|
||||
# migration実行
|
||||
sql-migrate up -config=./dictation_server/db/dbconfig.yml -env=ci
|
||||
@ -18,6 +18,7 @@ import DictationPage from "pages/DictationPage";
|
||||
import PartnerPage from "pages/PartnerPage";
|
||||
import WorkflowPage from "pages/WorkflowPage";
|
||||
import TypistGroupSettingPage from "pages/TypistGroupSettingPage";
|
||||
import WorktypeIdSettingPage from "pages/WorkTypeIdSettingPage";
|
||||
|
||||
const AppRouter: React.FC = () => (
|
||||
<Routes>
|
||||
@ -66,6 +67,10 @@ const AppRouter: React.FC = () => (
|
||||
path="/workflow/typist-group"
|
||||
element={<RouteAuthGuard component={<TypistGroupSettingPage />} />}
|
||||
/>
|
||||
<Route
|
||||
path="/workflow/worktype-id"
|
||||
element={<RouteAuthGuard component={<WorktypeIdSettingPage />} />}
|
||||
/>
|
||||
<Route
|
||||
path="/partners"
|
||||
element={<RouteAuthGuard component={<PartnerPage />} />}
|
||||
|
||||
@ -462,6 +462,25 @@ export interface CreateTypistGroupRequest {
|
||||
*/
|
||||
'typistIds': Array<number>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface CreateWorktypesRequest
|
||||
*/
|
||||
export interface CreateWorktypesRequest {
|
||||
/**
|
||||
* WorktypeID
|
||||
* @type {string}
|
||||
* @memberof CreateWorktypesRequest
|
||||
*/
|
||||
'worktypeId': string;
|
||||
/**
|
||||
* Worktypeの説明
|
||||
* @type {string}
|
||||
* @memberof CreateWorktypesRequest
|
||||
*/
|
||||
'description'?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@ -873,15 +892,15 @@ export interface GetUsersResponse {
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface GetWorkTypesResponse
|
||||
* @interface GetWorktypesResponse
|
||||
*/
|
||||
export interface GetWorkTypesResponse {
|
||||
export interface GetWorktypesResponse {
|
||||
/**
|
||||
*
|
||||
* @type {Array<WorkType>}
|
||||
* @memberof GetWorkTypesResponse
|
||||
* @type {Array<Worktype>}
|
||||
* @memberof GetWorktypesResponse
|
||||
*/
|
||||
'workTypes': Array<WorkType>;
|
||||
'worktypes': Array<Worktype>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@ -1609,25 +1628,25 @@ export interface User {
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface WorkType
|
||||
* @interface Worktype
|
||||
*/
|
||||
export interface WorkType {
|
||||
export interface Worktype {
|
||||
/**
|
||||
* WorkTypeのID
|
||||
* WorktypeのID
|
||||
* @type {number}
|
||||
* @memberof WorkType
|
||||
* @memberof Worktype
|
||||
*/
|
||||
'id': number;
|
||||
/**
|
||||
* WorkTypeID
|
||||
* WorktypeID
|
||||
* @type {string}
|
||||
* @memberof WorkType
|
||||
* @memberof Worktype
|
||||
*/
|
||||
'workTypeId': string;
|
||||
'worktypeId': string;
|
||||
/**
|
||||
* WorkTypeの説明
|
||||
* Worktypeの説明
|
||||
* @type {string}
|
||||
* @memberof WorkType
|
||||
* @memberof Worktype
|
||||
*/
|
||||
'description'?: string;
|
||||
}
|
||||
@ -1794,6 +1813,46 @@ export const AccountsApiAxiosParamCreator = function (configuration?: Configurat
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {CreateWorktypesRequest} createWorktypesRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
createWorktype: async (createWorktypesRequest: CreateWorktypesRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'createWorktypesRequest' is not null or undefined
|
||||
assertParamExists('createWorktype', 'createWorktypesRequest', createWorktypesRequest)
|
||||
const localVarPath = `/accounts/worktypes`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
// authentication bearer required
|
||||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(createWorktypesRequest, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
@ -2256,6 +2315,17 @@ export const AccountsApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.createTypistGroup(createTypistGroupRequest, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {CreateWorktypesRequest} createWorktypesRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async createWorktype(createWorktypesRequest: CreateWorktypesRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.createWorktype(createWorktypesRequest, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
@ -2346,7 +2416,7 @@ export const AccountsApiFp = function(configuration?: Configuration) {
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async getWorktypes(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<GetWorkTypesResponse>> {
|
||||
async getWorktypes(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<GetWorktypesResponse>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getWorktypes(options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
@ -2423,6 +2493,16 @@ export const AccountsApiFactory = function (configuration?: Configuration, baseP
|
||||
createTypistGroup(createTypistGroupRequest: CreateTypistGroupRequest, options?: any): AxiosPromise<object> {
|
||||
return localVarFp.createTypistGroup(createTypistGroupRequest, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {CreateWorktypesRequest} createWorktypesRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
createWorktype(createWorktypesRequest: CreateWorktypesRequest, options?: any): AxiosPromise<object> {
|
||||
return localVarFp.createWorktype(createWorktypesRequest, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
@ -2505,7 +2585,7 @@ export const AccountsApiFactory = function (configuration?: Configuration, baseP
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getWorktypes(options?: any): AxiosPromise<GetWorkTypesResponse> {
|
||||
getWorktypes(options?: any): AxiosPromise<GetWorktypesResponse> {
|
||||
return localVarFp.getWorktypes(options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
@ -2587,6 +2667,18 @@ export class AccountsApi extends BaseAPI {
|
||||
return AccountsApiFp(this.configuration).createTypistGroup(createTypistGroupRequest, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {CreateWorktypesRequest} createWorktypesRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof AccountsApi
|
||||
*/
|
||||
public createWorktype(createWorktypesRequest: CreateWorktypesRequest, options?: AxiosRequestConfig) {
|
||||
return AccountsApiFp(this.configuration).createWorktype(createWorktypesRequest, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
|
||||
@ -14,6 +14,7 @@ import dictation from "features/dictation/dictationSlice";
|
||||
import partner from "features/partner/partnerSlice";
|
||||
import licenseOrderHistory from "features/license/licenseOrderHistory/licenseOrderHistorySlice";
|
||||
import typistGroup from "features/workflow/typistGroup/typistGroupSlice";
|
||||
import worktype from "features/workflow/worktype/worktypeSlice";
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
@ -32,6 +33,7 @@ export const store = configureStore({
|
||||
dictation,
|
||||
partner,
|
||||
typistGroup,
|
||||
worktype,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
12
dictation_client/src/assets/images/worktype_add.svg
Normal file
12
dictation_client/src/assets/images/worktype_add.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.7.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="レイヤー_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
||||
y="0px" viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#282828;}
|
||||
</style>
|
||||
<path class="st0" d="M9,40c-0.8,0-1.5-0.3-2.1-0.9C6.3,38.5,6,37.8,6,37V7c0-0.8,0.3-1.5,0.9-2.1S8.2,4,9,4h30
|
||||
c0.8,0,1.5,0.3,2.1,0.9C41.7,5.5,42,6.2,42,7v15.1c-0.3,0-0.5-0.1-0.7-0.1s-0.5,0-0.7,0H39V7H9v30h6.9c0.2,0.5,0.3,1,0.5,1.5
|
||||
s0.4,1,0.7,1.5H9z M9,34v3V7V34z M14.5,31.5H16c0.4-1.2,1-2.5,1.8-3.7s1.8-2.3,2.8-3.3h-6.1C14.5,24.5,14.5,31.5,14.5,31.5z
|
||||
M14.5,19.5h7v-7h-7V19.5z M26.5,19.5h7v-7h-7V19.5z M29.6,41.9v-6.5H23v-3.9h6.5V25h3.9v6.5H40v3.9h-6.5v6.5H29.6z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 880 B |
@ -51,4 +51,6 @@ export const errorCodes = [
|
||||
"E010809", // ライセンス発行キャンセル不可エラー(ステータスが変えられている場合)
|
||||
"E010810", // ライセンス発行キャンセル不可エラー(発行から一定期間経過した場合)
|
||||
"E010811", // ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合)
|
||||
"E011001", // ワークタイプ重複エラー
|
||||
"E011002", // ワークタイプ登録上限超過エラー
|
||||
] as const;
|
||||
|
||||
4
dictation_client/src/features/workflow/worktype/index.ts
Normal file
4
dictation_client/src/features/workflow/worktype/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from "./worktypeSlice";
|
||||
export * from "./state";
|
||||
export * from "./selectors";
|
||||
export * from "./operations";
|
||||
112
dictation_client/src/features/workflow/worktype/operations.ts
Normal file
112
dictation_client/src/features/workflow/worktype/operations.ts
Normal file
@ -0,0 +1,112 @@
|
||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import type { RootState } from "app/store";
|
||||
import { openSnackbar } from "features/ui/uiSlice";
|
||||
import { getTranslationID } from "translation";
|
||||
import { AccountsApi, GetWorktypesResponse } from "../../../api/api";
|
||||
import { Configuration } from "../../../api/configuration";
|
||||
import { ErrorObject, createErrorObject } from "../../../common/errors";
|
||||
|
||||
export const listWorktypesAsync = createAsyncThunk<
|
||||
GetWorktypesResponse,
|
||||
void,
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("workflow/listWorktypesAsync", 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 worktypes = (
|
||||
await accountsApi.getWorktypes({
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
})
|
||||
).data;
|
||||
|
||||
return worktypes;
|
||||
} catch (e) {
|
||||
// e ⇒ errorObjectに変換"
|
||||
const error = createErrorObject(e);
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: getTranslationID("common.message.internalServerError"),
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
export const addWorktypeAsync = createAsyncThunk<
|
||||
{
|
||||
// return empty
|
||||
},
|
||||
void,
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("workflow/addWorktypeAsync", 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);
|
||||
// stateからworktypeIdとdescriptionを取得する
|
||||
const { worktypeId, description } = state.worktype.apps;
|
||||
|
||||
try {
|
||||
await accountsApi.createWorktype(
|
||||
{
|
||||
worktypeId,
|
||||
description,
|
||||
},
|
||||
{
|
||||
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");
|
||||
|
||||
// 既に同じworktypeIdが存在する場合
|
||||
if (error.code === "E011001") {
|
||||
errorMessage = getTranslationID(
|
||||
"worktypeIdSetting.message.alreadyWorktypeIdExistError"
|
||||
);
|
||||
}
|
||||
// worktypeIdが上限に達している場合
|
||||
if (error.code === "E011002") {
|
||||
errorMessage = getTranslationID(
|
||||
"worktypeIdSetting.message.worktypeIDLimitError"
|
||||
);
|
||||
}
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: errorMessage,
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
25
dictation_client/src/features/workflow/worktype/selectors.ts
Normal file
25
dictation_client/src/features/workflow/worktype/selectors.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { RootState } from "app/store";
|
||||
|
||||
export const selectWorktypes = (state: RootState) =>
|
||||
state.worktype.domain.worktypes;
|
||||
export const selectIsLoading = (state: RootState) =>
|
||||
state.worktype.apps.isLoading;
|
||||
|
||||
export const selectWorktypeId = (state: RootState) =>
|
||||
state.worktype.apps.worktypeId;
|
||||
|
||||
export const selectDescription = (state: RootState) =>
|
||||
state.worktype.apps.description;
|
||||
|
||||
// worktypeIdの値をチェックする
|
||||
export const selectHasErrorWorktypeId = (state: RootState) => {
|
||||
const { worktypeId } = state.worktype.apps;
|
||||
// worktypeIdが空文字の場合はエラー
|
||||
const isEmptyWorktypeId = worktypeId === "";
|
||||
|
||||
// worktypeIdに\/ : * ? “< > | .が含まれている場合はエラー
|
||||
const incorrectPattern = /[\\/:*?"<>|.]|[^ -~]/;
|
||||
const hasIncorrectPatternWorktypeId = incorrectPattern.test(worktypeId);
|
||||
|
||||
return { isEmptyWorktypeId, hasIncorrectPatternWorktypeId };
|
||||
};
|
||||
16
dictation_client/src/features/workflow/worktype/state.ts
Normal file
16
dictation_client/src/features/workflow/worktype/state.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Worktype } from "api";
|
||||
|
||||
export interface WorktypeState {
|
||||
apps: Apps;
|
||||
domain: Domain;
|
||||
}
|
||||
|
||||
export interface Apps {
|
||||
isLoading: boolean;
|
||||
worktypeId: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface Domain {
|
||||
worktypes?: Worktype[];
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||
import { WorktypeState } from "./state";
|
||||
import { addWorktypeAsync, listWorktypesAsync } from "./operations";
|
||||
|
||||
const initialState: WorktypeState = {
|
||||
apps: {
|
||||
isLoading: false,
|
||||
worktypeId: "",
|
||||
},
|
||||
domain: {},
|
||||
};
|
||||
|
||||
export const worktypeSlice = createSlice({
|
||||
name: "worktype",
|
||||
initialState,
|
||||
reducers: {
|
||||
cleanupWorktype: (state) => {
|
||||
state.apps.worktypeId = initialState.apps.worktypeId;
|
||||
state.apps.description = undefined;
|
||||
},
|
||||
changeWorktypeId: (
|
||||
state,
|
||||
action: PayloadAction<{ worktypeId: string }>
|
||||
) => {
|
||||
const { worktypeId } = action.payload;
|
||||
state.apps.worktypeId = worktypeId;
|
||||
},
|
||||
changeDescription: (
|
||||
state,
|
||||
action: PayloadAction<{ description?: string }>
|
||||
) => {
|
||||
const { description } = action.payload;
|
||||
state.apps.description = description;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(listWorktypesAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(listWorktypesAsync.fulfilled, (state, action) => {
|
||||
const { worktypes } = action.payload;
|
||||
state.domain.worktypes = worktypes;
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(listWorktypesAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(addWorktypeAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(addWorktypeAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
export const { changeDescription, changeWorktypeId, cleanupWorktype } =
|
||||
worktypeSlice.actions;
|
||||
export default worktypeSlice.reducer;
|
||||
@ -185,7 +185,7 @@ export const ChangeTranscriptionistPopup: React.FC<
|
||||
<input
|
||||
type="button"
|
||||
name="submit"
|
||||
value={t(getTranslationID("dictationPage.label.saveChanges"))}
|
||||
value={t(getTranslationID("common.label.save"))}
|
||||
className={`${styles.formSubmit} ${styles.marginBtm1} ${
|
||||
!isLoading ? styles.isActive : ""
|
||||
}`}
|
||||
|
||||
@ -52,6 +52,8 @@ const DictationPage: React.FC = (): JSX.Element => {
|
||||
const isAdmin = isAdminUser();
|
||||
const isAuthor = isAuthorUser();
|
||||
const isTypist = isTypistUser();
|
||||
const isNone = !isAuthor && !isTypist;
|
||||
|
||||
// popup制御関係
|
||||
const [
|
||||
isChangeTranscriptionistPopupOpen,
|
||||
@ -1001,7 +1003,10 @@ const DictationPage: React.FC = (): JSX.Element => {
|
||||
<ul className={styles.menuInTable}>
|
||||
<li>
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
|
||||
<a onClick={() => onPlayBack(x.audioFileId)}>
|
||||
<a
|
||||
className={isNone ? styles.isDisable : ""}
|
||||
onClick={() => onPlayBack(x.audioFileId)}
|
||||
>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"dictationPage.label.playback"
|
||||
|
||||
@ -181,7 +181,7 @@ export const LicenseOrderHistory: React.FC<LicenseOrderHistoryProps> = (
|
||||
onClick={returnGui}
|
||||
>
|
||||
<img src={undo} alt="" className={styles.menuIcon} />
|
||||
{t(getTranslationID("orderHistoriesPage.label.return"))}
|
||||
{t(getTranslationID("common.label.return"))}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@ -126,11 +126,7 @@ export const LicenseSummary: React.FC<LicenseSummaryProps> = (
|
||||
alt=""
|
||||
className={styles.menuIcon}
|
||||
/>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"partnerLicense.label.returnButton"
|
||||
)
|
||||
)}
|
||||
{t(getTranslationID("common.label.return"))}
|
||||
</a>
|
||||
)}
|
||||
</li>
|
||||
|
||||
@ -277,9 +277,7 @@ const PartnerLicense: React.FC = (): JSX.Element => {
|
||||
alt=""
|
||||
className={styles.menuIcon}
|
||||
/>
|
||||
{t(
|
||||
getTranslationID("partnerLicense.label.returnButton")
|
||||
)}
|
||||
{t(getTranslationID("common.label.return"))}
|
||||
</a>
|
||||
)}
|
||||
</li>
|
||||
|
||||
@ -190,7 +190,7 @@ export const AddTypistGroupPopup: React.FC<AddTypistGroupPopupProps> = (
|
||||
<input
|
||||
type="button"
|
||||
name="submit"
|
||||
value={t(getTranslationID("typistGroupSetting.label.save"))}
|
||||
value={t(getTranslationID("common.label.save"))}
|
||||
className={`${styles.formSubmit} ${styles.marginBtm1} ${styles.isActive}`}
|
||||
onClick={addTypistGroup}
|
||||
/>
|
||||
|
||||
@ -184,7 +184,7 @@ export const EditTypistGroupPopup: React.FC<EditTypistGroupPopupProps> = (
|
||||
<input
|
||||
type="button"
|
||||
name="submit"
|
||||
value={t(getTranslationID("typistGroupSetting.label.save"))}
|
||||
value={t(getTranslationID("common.label.save"))}
|
||||
className={`${styles.formSubmit} ${styles.marginBtm1} ${styles.isActive}`}
|
||||
onClick={editTypistGroup}
|
||||
/>
|
||||
|
||||
@ -83,7 +83,7 @@ const TypistGroupSettingPage: React.FC = (): JSX.Element => {
|
||||
className={`${styles.menuLink} ${styles.isActive}`}
|
||||
>
|
||||
<img src={undo} alt="" className={styles.menuIcon} />
|
||||
{t(getTranslationID("typistGroupSetting.label.return"))}
|
||||
{t(getTranslationID("common.label.return"))}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
@ -127,11 +127,7 @@ const TypistGroupSettingPage: React.FC = (): JSX.Element => {
|
||||
onEditPopupOpen(group.id);
|
||||
}}
|
||||
>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"typistGroupSetting.label.edit"
|
||||
)
|
||||
)}
|
||||
{t(getTranslationID("common.label.edit"))}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@ -0,0 +1,144 @@
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styles from "styles/app.module.scss";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
addWorktypeAsync,
|
||||
changeDescription,
|
||||
changeWorktypeId,
|
||||
cleanupWorktype,
|
||||
listWorktypesAsync,
|
||||
selectDescription,
|
||||
selectHasErrorWorktypeId,
|
||||
selectWorktypeId,
|
||||
} from "features/workflow/worktype";
|
||||
import { AppDispatch } from "app/store";
|
||||
import { getTranslationID } from "translation";
|
||||
import close from "../../assets/images/close.svg";
|
||||
|
||||
// popupのpropsの型定義
|
||||
interface AddWorktypeIdPopupProps {
|
||||
onClose: () => void;
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
export const AddWorktypeIdPopup: React.FC<AddWorktypeIdPopupProps> = (
|
||||
props: AddWorktypeIdPopupProps
|
||||
): JSX.Element => {
|
||||
const { onClose, isOpen } = props;
|
||||
const [t] = useTranslation();
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
const worktypeId = useSelector(selectWorktypeId);
|
||||
const description = useSelector(selectDescription);
|
||||
// 追加ボタンを押したかどうか
|
||||
const [isPushAddButton, setIsPushAddButton] = useState<boolean>(false);
|
||||
// WorktypeIdのバリデーションチェック
|
||||
const { hasIncorrectPatternWorktypeId, isEmptyWorktypeId } = useSelector(
|
||||
selectHasErrorWorktypeId
|
||||
);
|
||||
|
||||
// ×ボタンを押した時の処理
|
||||
const closePopup = useCallback(() => {
|
||||
dispatch(cleanupWorktype());
|
||||
setIsPushAddButton(false);
|
||||
onClose();
|
||||
}, [onClose, dispatch]);
|
||||
|
||||
// 追加ボタンを押した時の処理
|
||||
const addWorktypeId = useCallback(async () => {
|
||||
setIsPushAddButton(true);
|
||||
if (isEmptyWorktypeId || hasIncorrectPatternWorktypeId) {
|
||||
return;
|
||||
}
|
||||
const { meta } = await dispatch(addWorktypeAsync());
|
||||
if (meta.requestStatus === "fulfilled") {
|
||||
dispatch(listWorktypesAsync());
|
||||
closePopup();
|
||||
}
|
||||
}, [closePopup, dispatch, hasIncorrectPatternWorktypeId, isEmptyWorktypeId]);
|
||||
|
||||
return (
|
||||
<div className={`${styles.modal} ${isOpen ? styles.isShow : ""}`}>
|
||||
<div className={styles.modalBox}>
|
||||
<p className={styles.modalTitle}>
|
||||
{t(getTranslationID("worktypeIdSetting.label.addWorktypeId"))}
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions */}
|
||||
<img
|
||||
src={close}
|
||||
className={styles.modalTitleIcon}
|
||||
alt="close"
|
||||
onClick={closePopup}
|
||||
/>
|
||||
</p>
|
||||
<form action="" name="" method="" className={styles.form}>
|
||||
<dl className={`${styles.formList} ${styles.hasbg}`}>
|
||||
<dt className={styles.formTitle} />
|
||||
<dt>{t(getTranslationID("worktypeIdSetting.label.worktypeId"))}</dt>
|
||||
<dd>
|
||||
<input
|
||||
type="text"
|
||||
size={40}
|
||||
maxLength={255}
|
||||
value={worktypeId ?? ""}
|
||||
className={styles.formInput}
|
||||
onChange={(e) => {
|
||||
dispatch(changeWorktypeId({ worktypeId: e.target.value }));
|
||||
}}
|
||||
/>
|
||||
{isPushAddButton && isEmptyWorktypeId && (
|
||||
<span className={styles.formError}>
|
||||
{t(getTranslationID("common.message.inputEmptyError"))}
|
||||
</span>
|
||||
)}
|
||||
{isPushAddButton && hasIncorrectPatternWorktypeId && (
|
||||
<span className={styles.formError}>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"worktypeIdSetting.message.worktypeIdIncorrectError"
|
||||
)
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
style={{ whiteSpace: "pre-line" }}
|
||||
className={styles.formComment}
|
||||
>
|
||||
{t(getTranslationID("worktypeIdSetting.label.worktypeIdTerms"))}
|
||||
</span>
|
||||
</dd>
|
||||
<dt className={styles.overLine}>
|
||||
{t(
|
||||
getTranslationID("worktypeIdSetting.label.descriptionOptional")
|
||||
)}
|
||||
</dt>
|
||||
<dd className={styles.last}>
|
||||
<input
|
||||
type="text"
|
||||
size={40}
|
||||
maxLength={255}
|
||||
value={description ?? ""}
|
||||
className={styles.formInput}
|
||||
onChange={(e) => {
|
||||
const description =
|
||||
e.target.value === "" ? undefined : e.target.value;
|
||||
dispatch(changeDescription({ description }));
|
||||
}}
|
||||
/>
|
||||
</dd>
|
||||
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||
<input
|
||||
type="button"
|
||||
name="Add Worktype"
|
||||
value={t(
|
||||
getTranslationID("worktypeIdSetting.label.addWorktype")
|
||||
)}
|
||||
className={`${styles.formSubmit} ${styles.marginBtm1} ${styles.isActive}`}
|
||||
onClick={addWorktypeId}
|
||||
/>
|
||||
</dd>
|
||||
</dl>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
194
dictation_client/src/pages/WorkTypeIdSettingPage/index.tsx
Normal file
194
dictation_client/src/pages/WorkTypeIdSettingPage/index.tsx
Normal file
@ -0,0 +1,194 @@
|
||||
import { UpdateTokenTimer } from "components/auth/updateTokenTimer";
|
||||
import Footer from "components/footer";
|
||||
import Header from "components/header";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { getTranslationID } from "translation";
|
||||
import styles from "styles/app.module.scss";
|
||||
import undo from "assets/images/undo.svg";
|
||||
import worktype_add from "assets/images/worktype_add.svg";
|
||||
import progress_activit from "assets/images/progress_activit.svg";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
listWorktypesAsync,
|
||||
selectIsLoading,
|
||||
selectWorktypes,
|
||||
} from "features/workflow/worktype";
|
||||
import { AppDispatch } from "app/store";
|
||||
import { AddWorktypeIdPopup } from "./addWorktypeIdPopup";
|
||||
|
||||
const WorktypeIdSettingPage: React.FC = (): JSX.Element => {
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
const [t] = useTranslation();
|
||||
const isLoading = useSelector(selectIsLoading);
|
||||
const worktypes = useSelector(selectWorktypes);
|
||||
const [selectedRow, setSelectedRow] = useState<number>(NaN);
|
||||
// 追加Popupの表示制御
|
||||
const [isShowAddPopup, setIsShowAddPopup] = useState<boolean>(false);
|
||||
useEffect(() => {
|
||||
dispatch(listWorktypesAsync());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<AddWorktypeIdPopup
|
||||
onClose={() => {
|
||||
setIsShowAddPopup(false);
|
||||
}}
|
||||
isOpen={isShowAddPopup}
|
||||
/>
|
||||
<div className={styles.wrap}>
|
||||
<Header userName="XXXXXXX" />
|
||||
<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("worktypeIdSetting.label.title"))}
|
||||
</p>
|
||||
</div>
|
||||
<section className={styles.workflow}>
|
||||
<div>
|
||||
<ul className={`${styles.menuAction} ${styles.worktype}`}>
|
||||
<li>
|
||||
<a
|
||||
href="/workflow"
|
||||
className={`${styles.menuLink} ${styles.isActive}`}
|
||||
>
|
||||
<img src={undo} alt="" className={styles.menuIcon} />
|
||||
{t(getTranslationID("common.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={() => {
|
||||
setIsShowAddPopup(true);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={worktype_add}
|
||||
alt=""
|
||||
className={styles.menuIcon}
|
||||
/>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"worktypeIdSetting.label.addWorktypeId"
|
||||
)
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
<li className={styles.selectMenu}>
|
||||
{`${t(
|
||||
getTranslationID(
|
||||
"worktypeIdSetting.label.activeWorktypeId"
|
||||
)
|
||||
)}:`}
|
||||
<select name="Active Worktype" className={styles.formInput}>
|
||||
{worktypes?.map((worktype) => (
|
||||
<option key={worktype.id} value={worktype.id}>
|
||||
{worktype.worktypeId}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</li>
|
||||
</ul>
|
||||
<table className={`${styles.table} ${styles.worktype}`}>
|
||||
<tr className={styles.tableHeader}>
|
||||
<th className={styles.noLine}>
|
||||
{t(
|
||||
getTranslationID("worktypeIdSetting.label.worktypeId")
|
||||
)}
|
||||
</th>
|
||||
<th className={styles.noLine}>
|
||||
{t(
|
||||
getTranslationID("worktypeIdSetting.label.description")
|
||||
)}
|
||||
</th>
|
||||
<th>{/** empty th */}</th>
|
||||
</tr>
|
||||
{worktypes?.map((worktype) => (
|
||||
<tr
|
||||
key={worktype.id}
|
||||
className={
|
||||
worktype.id === selectedRow ? styles.isSelected : ""
|
||||
}
|
||||
onMouseEnter={() => {
|
||||
setSelectedRow(worktype.id);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setSelectedRow(NaN);
|
||||
}}
|
||||
>
|
||||
<td>{worktype.worktypeId}</td>
|
||||
<td>{worktype.description}</td>
|
||||
<td>
|
||||
<ul
|
||||
className={`${styles.menuAction} ${styles.inTable}`}
|
||||
>
|
||||
<li>
|
||||
<a
|
||||
className={`${styles.menuLink} ${styles.isActive}`}
|
||||
// onClick={}
|
||||
>
|
||||
{t(getTranslationID("common.label.edit"))}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className={`${styles.menuLink} ${styles.isActive}`}
|
||||
// onClick={}
|
||||
>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"worktypeIdSetting.label.optionItem"
|
||||
)
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className={`${styles.menuLink} ${styles.isActive}`}
|
||||
// onClick={}
|
||||
>
|
||||
{t(getTranslationID("common.label.delete"))}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</table>
|
||||
{!isLoading && worktypes?.length === 0 && (
|
||||
<p
|
||||
style={{
|
||||
margin: "10px",
|
||||
textAlign: "center",
|
||||
width: "1000px",
|
||||
}}
|
||||
>
|
||||
{t(getTranslationID("common.message.listEmpty"))}
|
||||
</p>
|
||||
)}
|
||||
{isLoading && (
|
||||
<img
|
||||
src={progress_activit}
|
||||
className={styles.icLoading}
|
||||
alt="Loading"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default WorktypeIdSettingPage;
|
||||
@ -15,6 +15,11 @@ const WorkflowPage: React.FC = (): JSX.Element => (
|
||||
Transcriptionist Group Setting
|
||||
</a>
|
||||
</span>
|
||||
<span>
|
||||
<a style={{ margin: 20 }} href="/workflow/worktype-id">
|
||||
Worktype ID Setting
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
|
||||
@ -5,15 +5,19 @@
|
||||
"passwordIncorrectError": "(de)Error Message",
|
||||
"emailIncorrectError": "(de)Error Message",
|
||||
"internalServerError": "(de)処理に失敗しました。時間をおいて再実行しても解決しない場合はシステム管理者にお問い合わせください。",
|
||||
"permissionDeniedError": "(de)操作を実行する権限がありません。",
|
||||
"listEmpty": "(de)検索結果が0件です。",
|
||||
"dialogConfirm": "(de)操作を実行しますか?",
|
||||
"success": "(de)処理に成功しました。",
|
||||
"permissionDeniedError": "(de)操作を実行する権限がありません。"
|
||||
"success": "(de)処理に成功しました。"
|
||||
},
|
||||
"label": {
|
||||
"cancel": "(de)Cancel",
|
||||
"headerTitle": "(de)ODMS Cloud",
|
||||
"copyRight": "(de)OM Digital Solutions 2023",
|
||||
"edit": "(de)Edit",
|
||||
"save": "(de)Save",
|
||||
"delete": "(de)Delete",
|
||||
"return": "(de)Return",
|
||||
"tier1": "(de)Admin",
|
||||
"tier2": "(de)BC",
|
||||
"tier3": "(de)Distributor",
|
||||
@ -120,8 +124,6 @@
|
||||
"label": {
|
||||
"title": "(de)User",
|
||||
"addUser": "(de)Add User",
|
||||
"edit": "(de)Edit",
|
||||
"delete": "(de)Delete",
|
||||
"licenseAllocation": "(de)License Allocation",
|
||||
"name": "(de)Name",
|
||||
"role": "(de)Role",
|
||||
@ -236,8 +238,7 @@
|
||||
"changeTranscriptionist": "(de)Change Transcriptionist",
|
||||
"deleteDictation": "(de)Delete Dictation",
|
||||
"selectedTranscriptionist": "(de)Selected",
|
||||
"poolTranscriptionist": "(de)Pool",
|
||||
"saveChanges": "(de)Save"
|
||||
"poolTranscriptionist": "(de)Pool"
|
||||
}
|
||||
},
|
||||
"cardLicenseIssuePopupPage": {
|
||||
@ -294,7 +295,6 @@
|
||||
"label": {
|
||||
"title": "(de)License",
|
||||
"subTitle": "(de)License for partners",
|
||||
"returnButton": "(de)Return",
|
||||
"orderLicenseButton": "(de)Order License",
|
||||
"orderHistoryButton": "(de)Order History",
|
||||
"IssueLicenseCardButton": "(de)License Card",
|
||||
@ -314,7 +314,6 @@
|
||||
"label": {
|
||||
"title": "(de)License",
|
||||
"orderHistory": "(de)Order History",
|
||||
"return": "(de)Return",
|
||||
"orderDate": "(de)Order date",
|
||||
"issueDate": "(de)Issue date",
|
||||
"numberOfOrder": "(de)Number of Order",
|
||||
@ -367,22 +366,52 @@
|
||||
"typistGroupSetting": {
|
||||
"label": {
|
||||
"title": "(de)Transctiprionist Group",
|
||||
"return": "(de)Return",
|
||||
"addGroup": "(de)Add Group",
|
||||
"groupName": "(de)Group Name",
|
||||
"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"
|
||||
"editTypistGroup": "(de)Edit Transcriptionist Group"
|
||||
},
|
||||
"message": {
|
||||
"selectedTypistEmptyError": "(de)TranscriptionistがいないTranscriptionistGroupは保存できません。1名以上をTranscriptionistとして選択してください。",
|
||||
"groupSaveFailedError": "(de)TypistGroupの保存に失敗しました。画面を更新し、再度実行してください"
|
||||
}
|
||||
},
|
||||
"worktypeIdSetting": {
|
||||
"label": {
|
||||
"title": "(de)Worktype ID Setting",
|
||||
"activeWorktypeId": "(de)Active Worktype ID",
|
||||
"addWorktypeId": "(de)Add Worktype ID",
|
||||
"worktypeId": "(de)Worktype ID",
|
||||
"description": "(de)Description",
|
||||
"descriptionOptional": "(de)Description (Optional)",
|
||||
"optionItem": "(de)Option Item",
|
||||
"worktypeIdTerms": "(de)WorktypeID should be alphanumeric and symbols,\n but not include: \\ / : * ? “ < > | .",
|
||||
"addWorktype": "(de)Add Worktype"
|
||||
},
|
||||
"message": {
|
||||
"worktypeIdIncorrectError": "(de)入力されたWorktypeIDがルールを満たしていません。下記のルールを満たすWorktypeIDを入力してください",
|
||||
"alreadyWorktypeIdExistError": "(de)このWorktype IDは既に登録されています。他のWorktype IDで登録してください。",
|
||||
"worktypeIDLimitError": "(de)Worktype IDが登録件数の上限に達しているため追加できません。"
|
||||
}
|
||||
},
|
||||
"partnerPage": {
|
||||
"label": {
|
||||
"title": "(de)Partners",
|
||||
"addAccount": "(de)Add Account",
|
||||
"name": "(de)Name",
|
||||
"category": "(de)Category",
|
||||
"accountId": "(de)Account ID",
|
||||
"country": "(de)Country",
|
||||
"primaryAdmin": "(de)Primary Admin",
|
||||
"email": "(de)E-mail",
|
||||
"dealerManagement": "(de)Dealer Management",
|
||||
"partners": "(de)partners",
|
||||
"deleteAccount": "(de)Delete Account"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,15 +5,19 @@
|
||||
"passwordIncorrectError": "Error Message",
|
||||
"emailIncorrectError": "Error Message",
|
||||
"internalServerError": "処理に失敗しました。時間をおいて再実行しても解決しない場合はシステム管理者にお問い合わせください。",
|
||||
"permissionDeniedError": "操作を実行する権限がありません。",
|
||||
"listEmpty": "検索結果が0件です。",
|
||||
"dialogConfirm": "操作を実行しますか?",
|
||||
"success": "処理に成功しました。",
|
||||
"permissionDeniedError": "操作を実行する権限がありません。"
|
||||
"success": "処理に成功しました。"
|
||||
},
|
||||
"label": {
|
||||
"cancel": "Cancel",
|
||||
"headerTitle": "ODMS Cloud",
|
||||
"copyRight": "OM Digital Solutions 2023",
|
||||
"edit": "Edit",
|
||||
"save": "Save",
|
||||
"delete": "Delete",
|
||||
"return": "Return",
|
||||
"tier1": "Admin",
|
||||
"tier2": "BC",
|
||||
"tier3": "Distributor",
|
||||
@ -120,8 +124,6 @@
|
||||
"label": {
|
||||
"title": "User",
|
||||
"addUser": "Add User",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"licenseAllocation": "License Allocation",
|
||||
"name": "Name",
|
||||
"role": "Role",
|
||||
@ -236,8 +238,7 @@
|
||||
"changeTranscriptionist": "Change Transcriptionist",
|
||||
"deleteDictation": "Delete Dictation",
|
||||
"selectedTranscriptionist": "Selected",
|
||||
"poolTranscriptionist": "Pool",
|
||||
"saveChanges": "Save"
|
||||
"poolTranscriptionist": "Pool"
|
||||
}
|
||||
},
|
||||
"cardLicenseIssuePopupPage": {
|
||||
@ -294,7 +295,6 @@
|
||||
"label": {
|
||||
"title": "License",
|
||||
"subTitle": "License for partners",
|
||||
"returnButton": "Return",
|
||||
"orderLicenseButton": "Order License",
|
||||
"orderHistoryButton": "Order History",
|
||||
"IssueLicenseCardButton": "License Card",
|
||||
@ -314,7 +314,6 @@
|
||||
"label": {
|
||||
"title": "License",
|
||||
"orderHistory": "Order History",
|
||||
"return": "Return",
|
||||
"orderDate": "Order date",
|
||||
"issueDate": "Issue date",
|
||||
"numberOfOrder": "Number of Order",
|
||||
@ -367,22 +366,52 @@
|
||||
"typistGroupSetting": {
|
||||
"label": {
|
||||
"title": "Transctiprionist Group",
|
||||
"return": "Return",
|
||||
"addGroup": "Add Group",
|
||||
"groupName": "Group Name",
|
||||
"edit": "Edit",
|
||||
"addTypistGroup": "Add Transcriptionist Group",
|
||||
"transcriptionist": "Transcriptionist",
|
||||
"selected": "Selected",
|
||||
"pool": "Pool",
|
||||
"add": "Add",
|
||||
"remove": "Remove",
|
||||
"editTypistGroup": "Edit Transcriptionist Group",
|
||||
"save": "Save"
|
||||
"editTypistGroup": "Edit Transcriptionist Group"
|
||||
},
|
||||
"message": {
|
||||
"selectedTypistEmptyError": "TranscriptionistがいないTranscriptionistGroupは保存できません。1名以上をTranscriptionistとして選択してください。",
|
||||
"groupSaveFailedError": "TypistGroupの保存に失敗しました。画面を更新し、再度実行してください"
|
||||
}
|
||||
},
|
||||
"worktypeIdSetting": {
|
||||
"label": {
|
||||
"title": "Worktype ID Setting",
|
||||
"activeWorktypeId": "Active Worktype ID",
|
||||
"addWorktypeId": "Add Worktype ID",
|
||||
"worktypeId": "Worktype ID",
|
||||
"description": "Description",
|
||||
"descriptionOptional": "Description (Optional)",
|
||||
"optionItem": "Option Item",
|
||||
"worktypeIdTerms": "WorktypeID should be alphanumeric and symbols,\n but not include: \\ / : * ? “ < > | .",
|
||||
"addWorktype": "Add Worktype"
|
||||
},
|
||||
"message": {
|
||||
"worktypeIdIncorrectError": "入力されたWorktypeIDがルールを満たしていません。下記のルールを満たすWorktypeIDを入力してください",
|
||||
"alreadyWorktypeIdExistError": "このWorktype IDは既に登録されています。他のWorktype IDで登録してください。",
|
||||
"worktypeIDLimitError": "Worktype IDが登録件数の上限に達しているため追加できません。"
|
||||
}
|
||||
},
|
||||
"partnerPage": {
|
||||
"label": {
|
||||
"title": "Partners",
|
||||
"addAccount": "Add Account",
|
||||
"name": "Name",
|
||||
"category": "Category",
|
||||
"accountId": "Account ID",
|
||||
"country": "Country",
|
||||
"primaryAdmin": "Primary Admin",
|
||||
"email": "E-mail",
|
||||
"dealerManagement": "Dealer Management",
|
||||
"partners": "partners",
|
||||
"deleteAccount": "Delete Account"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,15 +5,19 @@
|
||||
"passwordIncorrectError": "(es)Error Message",
|
||||
"emailIncorrectError": "(es)Error Message",
|
||||
"internalServerError": "(es)処理に失敗しました。時間をおいて再実行しても解決しない場合はシステム管理者にお問い合わせください。",
|
||||
"permissionDeniedError": "(es)操作を実行する権限がありません。",
|
||||
"listEmpty": "(es)検索結果が0件です。",
|
||||
"dialogConfirm": "(es)操作を実行しますか?",
|
||||
"success": "(es)処理に成功しました。",
|
||||
"permissionDeniedError": "(es)操作を実行する権限がありません。"
|
||||
"success": "(es)処理に成功しました。"
|
||||
},
|
||||
"label": {
|
||||
"cancel": "(es)Cancel",
|
||||
"headerTitle": "(es)ODMS Cloud",
|
||||
"copyRight": "(es)OM Digital Solutions 2023",
|
||||
"edit": "(es)Edit",
|
||||
"save": "(es)Save",
|
||||
"delete": "(es)Delete",
|
||||
"return": "(es)Return",
|
||||
"tier1": "(es)Admin",
|
||||
"tier2": "(es)BC",
|
||||
"tier3": "(es)Distributor",
|
||||
@ -120,8 +124,6 @@
|
||||
"label": {
|
||||
"title": "(es)User",
|
||||
"addUser": "(es)Add User",
|
||||
"edit": "(es)Edit",
|
||||
"delete": "(es)Delete",
|
||||
"licenseAllocation": "(es)License Allocation",
|
||||
"name": "(es)Name",
|
||||
"role": "(es)Role",
|
||||
@ -236,8 +238,7 @@
|
||||
"changeTranscriptionist": "(es)Change Transcriptionist",
|
||||
"deleteDictation": "(es)Delete Dictation",
|
||||
"selectedTranscriptionist": "(es)Selected",
|
||||
"poolTranscriptionist": "(es)Pool",
|
||||
"saveChanges": "(es)Save"
|
||||
"poolTranscriptionist": "(es)Pool"
|
||||
}
|
||||
},
|
||||
"cardLicenseIssuePopupPage": {
|
||||
@ -294,7 +295,6 @@
|
||||
"label": {
|
||||
"title": "(es)License",
|
||||
"subTitle": "(es)License for partners",
|
||||
"returnButton": "(es)Return",
|
||||
"orderLicenseButton": "(es)Order License",
|
||||
"orderHistoryButton": "(es)Order History",
|
||||
"IssueLicenseCardButton": "(es)License Card",
|
||||
@ -314,7 +314,6 @@
|
||||
"label": {
|
||||
"title": "(es)License",
|
||||
"orderHistory": "(es)Order History",
|
||||
"return": "(es)Return",
|
||||
"orderDate": "(es)Order date",
|
||||
"issueDate": "(es)Issue date",
|
||||
"numberOfOrder": "(es)Number of Order",
|
||||
@ -367,22 +366,52 @@
|
||||
"typistGroupSetting": {
|
||||
"label": {
|
||||
"title": "(es)Transctiprionist Group",
|
||||
"return": "(es)Return",
|
||||
"addGroup": "(es)Add Group",
|
||||
"groupName": "(es)Group Name",
|
||||
"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"
|
||||
"editTypistGroup": "(es)Edit Transcriptionist Group"
|
||||
},
|
||||
"message": {
|
||||
"selectedTypistEmptyError": "(es)TranscriptionistがいないTranscriptionistGroupは保存できません。1名以上をTranscriptionistとして選択してください。",
|
||||
"groupSaveFailedError": "(es)TypistGroupの保存に失敗しました。画面を更新し、再度実行してください"
|
||||
}
|
||||
},
|
||||
"worktypeIdSetting": {
|
||||
"label": {
|
||||
"title": "(es)Worktype ID Setting",
|
||||
"activeWorktypeId": "(es)Active Worktype ID",
|
||||
"addWorktypeId": "(es)Add Worktype ID",
|
||||
"worktypeId": "(es)Worktype ID",
|
||||
"description": "(es)Description",
|
||||
"descriptionOptional": "(es)Description (Optional)",
|
||||
"optionItem": "(es)Option Item",
|
||||
"worktypeIdTerms": "(es)WorktypeID should be alphanumeric and symbols,\n but not include: \\ / : * ? “ < > | .",
|
||||
"addWorktype": "(es)Add Worktype"
|
||||
},
|
||||
"message": {
|
||||
"worktypeIdIncorrectError": "(es)入力されたWorktypeIDがルールを満たしていません。下記のルールを満たすWorktypeIDを入力してください",
|
||||
"alreadyWorktypeIdExistError": "(es)このWorktype IDは既に登録されています。他のWorktype IDで登録してください。",
|
||||
"worktypeIDLimitError": "(es)Worktype IDが登録件数の上限に達しているため追加できません。"
|
||||
}
|
||||
},
|
||||
"partnerPage": {
|
||||
"label": {
|
||||
"title": "(es)Partners",
|
||||
"addAccount": "(es)Add Account",
|
||||
"name": "(es)Name",
|
||||
"category": "(es)Category",
|
||||
"accountId": "(es)Account ID",
|
||||
"country": "(es)Country",
|
||||
"primaryAdmin": "(es)Primary Admin",
|
||||
"email": "(es)E-mail",
|
||||
"dealerManagement": "(es)Dealer Management",
|
||||
"partners": "(es)partners",
|
||||
"deleteAccount": "(es)Delete Account"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,15 +5,19 @@
|
||||
"passwordIncorrectError": "(fr)Error Message",
|
||||
"emailIncorrectError": "(fr)Error Message",
|
||||
"internalServerError": "(fr)処理に失敗しました。時間をおいて再実行しても解決しない場合はシステム管理者にお問い合わせください。",
|
||||
"permissionDeniedError": "(fr)操作を実行する権限がありません。",
|
||||
"listEmpty": "(fr)検索結果が0件です。",
|
||||
"dialogConfirm": "(fr)操作を実行しますか?",
|
||||
"success": "(fr)処理に成功しました。",
|
||||
"permissionDeniedError": "(fr)操作を実行する権限がありません。"
|
||||
"success": "(fr)処理に成功しました。"
|
||||
},
|
||||
"label": {
|
||||
"cancel": "(fr)Cancel",
|
||||
"headerTitle": "(fr)ODMS Cloud",
|
||||
"copyRight": "(fr)OM Digital Solutions 2023",
|
||||
"edit": "(fr)Edit",
|
||||
"save": "(fr)Save",
|
||||
"delete": "(fr)Delete",
|
||||
"return": "(fr)Return",
|
||||
"tier1": "(fr)Admin",
|
||||
"tier2": "(fr)BC",
|
||||
"tier3": "(fr)Distributor",
|
||||
@ -120,8 +124,6 @@
|
||||
"label": {
|
||||
"title": "(fr)User",
|
||||
"addUser": "(fr)Add User",
|
||||
"edit": "(fr)Edit",
|
||||
"delete": "(fr)Delete",
|
||||
"licenseAllocation": "(fr)License Allocation",
|
||||
"name": "(fr)Name",
|
||||
"role": "(fr)Role",
|
||||
@ -236,8 +238,7 @@
|
||||
"changeTranscriptionist": "(fr)Change Transcriptionist",
|
||||
"deleteDictation": "(fr)Delete Dictation",
|
||||
"selectedTranscriptionist": "(fr)Selected",
|
||||
"poolTranscriptionist": "(fr)Pool",
|
||||
"saveChanges": "(fr)Save"
|
||||
"poolTranscriptionist": "(fr)Pool"
|
||||
}
|
||||
},
|
||||
"cardLicenseIssuePopupPage": {
|
||||
@ -294,7 +295,6 @@
|
||||
"label": {
|
||||
"title": "(fr)License",
|
||||
"subTitle": "(fr)License for partners",
|
||||
"returnButton": "(fr)Return",
|
||||
"orderLicenseButton": "(fr)Order License",
|
||||
"orderHistoryButton": "(fr)Order History",
|
||||
"IssueLicenseCardButton": "(fr)License Card",
|
||||
@ -314,7 +314,6 @@
|
||||
"label": {
|
||||
"title": "(fr)License",
|
||||
"orderHistory": "(fr)Order History",
|
||||
"return": "(fr)Return",
|
||||
"orderDate": "(fr)Order date",
|
||||
"issueDate": "(fr)Issue date",
|
||||
"numberOfOrder": "(fr)Number of Order",
|
||||
@ -367,22 +366,52 @@
|
||||
"typistGroupSetting": {
|
||||
"label": {
|
||||
"title": "(fr)Transctiprionist Group",
|
||||
"return": "(fr)Return",
|
||||
"addGroup": "(fr)Add Group",
|
||||
"groupName": "(fr)Group Name",
|
||||
"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"
|
||||
"editTypistGroup": "(fr)Edit Transcriptionist Group"
|
||||
},
|
||||
"message": {
|
||||
"selectedTypistEmptyError": "(fr)TranscriptionistがいないTranscriptionistGroupは保存できません。1名以上をTranscriptionistとして選択してください。",
|
||||
"groupSaveFailedError": "(fr)TypistGroupの保存に失敗しました。画面を更新し、再度実行してください"
|
||||
}
|
||||
},
|
||||
"worktypeIdSetting": {
|
||||
"label": {
|
||||
"title": "(fr)Worktype ID Setting",
|
||||
"activeWorktypeId": "(fr)Active Worktype ID",
|
||||
"addWorktypeId": "(fr)Add Worktype ID",
|
||||
"worktypeId": "(fr)Worktype ID",
|
||||
"description": "(fr)Description",
|
||||
"descriptionOptional": "(fr)Description (Optional)",
|
||||
"optionItem": "(fr)Option Item",
|
||||
"worktypeIdTerms": "(fr)WorktypeID should be alphanumeric and symbols,\n but not include: \\ / : * ? “ < > | .",
|
||||
"addWorktype": "(fr)Add Worktype"
|
||||
},
|
||||
"message": {
|
||||
"worktypeIdIncorrectError": "(fr)入力されたWorktypeIDがルールを満たしていません。下記のルールを満たすWorktypeIDを入力してください",
|
||||
"alreadyWorktypeIdExistError": "(fr)このWorktype IDは既に登録されています。他のWorktype IDで登録してください。",
|
||||
"worktypeIDLimitError": "(fr)Worktype IDが登録件数の上限に達しているため追加できません。"
|
||||
}
|
||||
},
|
||||
"partnerPage": {
|
||||
"label": {
|
||||
"title": "(fr)Partners",
|
||||
"addAccount": "(fr)Add Account",
|
||||
"name": "(fr)Name",
|
||||
"category": "(fr)Category",
|
||||
"accountId": "(fr)Account ID",
|
||||
"country": "(fr)Country",
|
||||
"primaryAdmin": "(fr)Primary Admin",
|
||||
"email": "(fr)E-mail",
|
||||
"dealerManagement": "(fr)Dealer Management",
|
||||
"partners": "(fr)partners",
|
||||
"deleteAccount": "(fr)Delete Account"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,4 +24,4 @@ STORAGE_ACCOUNT_KEY_AU=XXXXXXXXXXXXXXXXXXXXXXX
|
||||
STORAGE_ACCOUNT_KEY_EU=XXXXXXXXXXXXXXXXXXXXXXX
|
||||
STORAGE_ACCOUNT_ENDPOINT_US=https://AAAAAAAAAAAAA
|
||||
STORAGE_ACCOUNT_ENDPOINT_AU=https://AAAAAAAAAAAAA
|
||||
STORAGE_ACCOUNT_ENDPOINT_EU=https://AAAAAAAAAAAAA
|
||||
STORAGE_ACCOUNT_ENDPOINT_EU=https://AAAAAAAAAAAAA
|
||||
15
dictation_server/db/migrations/032-create_option_items.sql
Normal file
15
dictation_server/db/migrations/032-create_option_items.sql
Normal file
@ -0,0 +1,15 @@
|
||||
-- +migrate Up
|
||||
CREATE TABLE IF NOT EXISTS `option_items` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY COMMENT 'オプションアイテムID',
|
||||
`worktype_id` BIGINT UNSIGNED NOT NULL COMMENT 'worktypeの内部ID',
|
||||
`item_label` VARCHAR(50) NOT NULL COMMENT 'アイテムラベル',
|
||||
`default_value_type` VARCHAR(16) NOT NULL COMMENT 'デフォルト値種別(Default/Blank/LastInput)',
|
||||
`initial_value` VARCHAR(50) NOT NULL 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 '更新時刻'
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci;
|
||||
|
||||
-- +migrate Down
|
||||
DROP TABLE `option_items`;
|
||||
111
dictation_server/package-lock.json
generated
111
dictation_server/package-lock.json
generated
@ -26,6 +26,8 @@
|
||||
"@types/jsonwebtoken": "^9.0.1",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"axios": "^1.3.4",
|
||||
"cache-manager": "^5.2.0",
|
||||
"cache-manager-redis-store": "^2.0.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
"connect-redis": "^6.1.3",
|
||||
@ -47,6 +49,7 @@
|
||||
"@nestjs/schematics": "^8.0.0",
|
||||
"@nestjs/swagger": "^6.3.0",
|
||||
"@nestjs/testing": "^9.3.12",
|
||||
"@types/cache-manager-redis-store": "^2.0.1",
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/express-session": "^1.17.5",
|
||||
@ -2859,6 +2862,22 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/cache-manager": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/cache-manager/-/cache-manager-4.0.2.tgz",
|
||||
"integrity": "sha512-fT5FMdzsiSX0AbgnS5gDvHl2Nco0h5zYyjwDQy4yPC7Ww6DeGMVKPRqIZtg9HOXDV2kkc18SL1B0N8f0BecrCA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/cache-manager-redis-store": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/cache-manager-redis-store/-/cache-manager-redis-store-2.0.1.tgz",
|
||||
"integrity": "sha512-8QuccvcPieh1xM/5kReE76SfdcIdEB0ePc+54ah/NBuK2eG+6O50SX4WKoJX81UxGdW3sh/WlDaDNqjnqxWNsA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/cache-manager": "*",
|
||||
"@types/redis": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/connect": {
|
||||
"version": "3.4.35",
|
||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
|
||||
@ -3062,6 +3081,15 @@
|
||||
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/redis": {
|
||||
"version": "2.8.32",
|
||||
"resolved": "https://registry.npmjs.org/@types/redis/-/redis-2.8.32.tgz",
|
||||
"integrity": "sha512-7jkMKxcGq9p242exlbsVzuJb57KqHRhNl4dHoQu2Y5v9bCAbtIXXH0R3HleSQW4CTOqpHIYUW3t6tpUj4BVQ+w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/semver": {
|
||||
"version": "7.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
|
||||
@ -4235,6 +4263,60 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/cache-manager": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-5.2.3.tgz",
|
||||
"integrity": "sha512-9OErI8fksFkxAMJ8Mco0aiZSdphyd90HcKiOMJQncSlU1yq/9lHHxrT8PDayxrmr9IIIZPOAEfXuGSD7g29uog==",
|
||||
"dependencies": {
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lru-cache": "^9.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/cache-manager-redis-store": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cache-manager-redis-store/-/cache-manager-redis-store-2.0.0.tgz",
|
||||
"integrity": "sha512-bWLWlUg6nCYHiJLCCYxY2MgvwvKnvlWwrbuynrzpjEIhfArD2GC9LtutIHFEPeyGVQN6C+WEw+P3r+BFBwhswg==",
|
||||
"dependencies": {
|
||||
"redis": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.3"
|
||||
}
|
||||
},
|
||||
"node_modules/cache-manager-redis-store/node_modules/denque": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz",
|
||||
"integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==",
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/cache-manager-redis-store/node_modules/redis": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz",
|
||||
"integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==",
|
||||
"dependencies": {
|
||||
"denque": "^1.5.0",
|
||||
"redis-commands": "^1.7.0",
|
||||
"redis-errors": "^1.2.0",
|
||||
"redis-parser": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/node-redis"
|
||||
}
|
||||
},
|
||||
"node_modules/cache-manager/node_modules/lru-cache": {
|
||||
"version": "9.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.2.tgz",
|
||||
"integrity": "sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==",
|
||||
"engines": {
|
||||
"node": "14 || >=16.14"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||
@ -8130,6 +8212,11 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
|
||||
},
|
||||
"node_modules/lodash.memoize": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||
@ -9802,6 +9889,30 @@
|
||||
"@redis/time-series": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/redis-commands": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz",
|
||||
"integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ=="
|
||||
},
|
||||
"node_modules/redis-errors": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
|
||||
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/redis-parser": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
|
||||
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
|
||||
"dependencies": {
|
||||
"redis-errors": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/reflect-metadata": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
|
||||
|
||||
@ -45,6 +45,8 @@
|
||||
"@types/jsonwebtoken": "^9.0.1",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"axios": "^1.3.4",
|
||||
"cache-manager": "^5.2.0",
|
||||
"cache-manager-redis-store": "^2.0.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
"connect-redis": "^6.1.3",
|
||||
@ -66,6 +68,7 @@
|
||||
"@nestjs/schematics": "^8.0.0",
|
||||
"@nestjs/swagger": "^6.3.0",
|
||||
"@nestjs/testing": "^9.3.12",
|
||||
"@types/cache-manager-redis-store": "^2.0.1",
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/express-session": "^1.17.5",
|
||||
|
||||
@ -838,6 +838,127 @@
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/accounts/worktypes/{id}": {
|
||||
"post": {
|
||||
"operationId": "updateWorktype",
|
||||
"summary": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Worktypeの内部ID",
|
||||
"schema": { "type": "number" }
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UpdateWorktypesRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功時のレスポンス",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UpdateWorktypeResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "WorktypeIDが重複 / WorktypeIDが空",
|
||||
"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/partners": {
|
||||
"get": {
|
||||
"operationId": "getPartners",
|
||||
"summary": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "limit",
|
||||
"required": true,
|
||||
"in": "query",
|
||||
"description": "取得件数",
|
||||
"schema": { "type": "number" }
|
||||
},
|
||||
{
|
||||
"name": "offset",
|
||||
"required": true,
|
||||
"in": "query",
|
||||
"description": "開始位置",
|
||||
"schema": { "type": "number" }
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功時のレスポンス",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/GetPartnersResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": [] }]
|
||||
}
|
||||
},
|
||||
"/users/confirm": {
|
||||
"post": {
|
||||
"operationId": "confirmUser",
|
||||
@ -2767,6 +2888,60 @@
|
||||
"required": ["worktypeId"]
|
||||
},
|
||||
"CreateWorktypeResponse": { "type": "object", "properties": {} },
|
||||
"UpdateWorktypesRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"worktypeId": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"description": "WorktypeID"
|
||||
},
|
||||
"description": { "type": "string", "description": "Worktypeの説明" }
|
||||
},
|
||||
"required": ["worktypeId"]
|
||||
},
|
||||
"UpdateWorktypeResponse": { "type": "object", "properties": {} },
|
||||
"Partner": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string", "description": "会社名" },
|
||||
"tier": { "type": "number", "description": "階層" },
|
||||
"accountId": { "type": "number", "description": "アカウントID" },
|
||||
"country": { "type": "string", "description": "国" },
|
||||
"primaryAdmin": {
|
||||
"type": "string",
|
||||
"description": "プライマリ管理者"
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "プライマリ管理者メールアドレス"
|
||||
},
|
||||
"dealerManagement": {
|
||||
"type": "boolean",
|
||||
"description": "代行操作許可"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"tier",
|
||||
"accountId",
|
||||
"country",
|
||||
"primaryAdmin",
|
||||
"email",
|
||||
"dealerManagement"
|
||||
]
|
||||
},
|
||||
"GetPartnersResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"total": { "type": "number", "description": "合計件数" },
|
||||
"partners": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/components/schemas/Partner" }
|
||||
}
|
||||
},
|
||||
"required": ["total", "partners"]
|
||||
},
|
||||
"ConfirmRequest": {
|
||||
"type": "object",
|
||||
"properties": { "token": { "type": "string" } },
|
||||
|
||||
@ -41,6 +41,7 @@ import { UserGroupsRepositoryModule } from './repositories/user_groups/user_grou
|
||||
import { SortCriteriaRepositoryModule } from './repositories/sort_criteria/sort_criteria.repository.module';
|
||||
import { TemplateFilesRepositoryModule } from './repositories/template_files/template_files.repository.module';
|
||||
import { WorktypesRepositoryModule } from './repositories/worktypes/worktypes.repository.module';
|
||||
import { OptionItemsRepositoryModule } from './repositories/option_items/option_items.repository.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -96,6 +97,7 @@ import { WorktypesRepositoryModule } from './repositories/worktypes/worktypes.re
|
||||
AuthGuardsModule,
|
||||
SortCriteriaRepositoryModule,
|
||||
WorktypesRepositoryModule,
|
||||
OptionItemsRepositoryModule,
|
||||
],
|
||||
controllers: [
|
||||
HealthController,
|
||||
|
||||
@ -48,5 +48,11 @@ export const ErrorCodes = [
|
||||
'E010806', // ライセンス割り当て不可エラー
|
||||
'E010807', // ライセンス割り当て解除済みエラー
|
||||
'E010808', // ライセンス注文キャンセル不可エラー
|
||||
'E010809', // ライセンス発行キャンセル不可エラー(ステータスが変えられている場合)
|
||||
'E010810', // ライセンス発行キャンセル不可エラー(発行から一定期間経過した場合)
|
||||
'E010811', // ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合)
|
||||
'E010908', // タイピストグループ不在エラー
|
||||
'E011001', // ワークタイプ重複エラー
|
||||
'E011002', // ワークタイプ登録上限超過エラー
|
||||
'E011003', // ワークタイプ不在エラー
|
||||
] as const;
|
||||
|
||||
@ -37,5 +37,11 @@ export const errors: Errors = {
|
||||
E010806: 'License is unavailable Error',
|
||||
E010807: 'License is already deallocated Error',
|
||||
E010808: 'Order cancel failed Error',
|
||||
E010809: 'Already license order status changed Error',
|
||||
E010810: 'Cancellation period expired error',
|
||||
E010811: 'Already license allocated Error',
|
||||
E010908: 'Typist Group not exist Error',
|
||||
E011001: 'Thiw WorkTypeID already used Error',
|
||||
E011002: 'WorkTypeID create limit exceeded Error',
|
||||
E011003: 'WorkTypeID not found Error',
|
||||
};
|
||||
|
||||
@ -30,6 +30,7 @@ import { NotificationhubService } from '../../gateways/notificationhub/notificat
|
||||
import { FilesService } from '../../features/files/files.service';
|
||||
import { LicensesService } from '../../features/licenses/licenses.service';
|
||||
import { TasksService } from '../../features/tasks/tasks.service';
|
||||
import { OptionItemsRepositoryModule } from '../../repositories/option_items/option_items.repository.module';
|
||||
|
||||
export const makeTestingModule = async (
|
||||
datasource: DataSource,
|
||||
@ -65,6 +66,7 @@ export const makeTestingModule = async (
|
||||
AuthGuardsModule,
|
||||
SortCriteriaRepositoryModule,
|
||||
WorktypesRepositoryModule,
|
||||
OptionItemsRepositoryModule,
|
||||
],
|
||||
providers: [
|
||||
AuthService,
|
||||
|
||||
@ -8,8 +8,7 @@ import {
|
||||
|
||||
@ValidatorConstraint()
|
||||
export class IsUniqueArray implements ValidatorConstraintInterface {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
validate(arr: any[], args: ValidationArguments) {
|
||||
validate(arr: any[]) {
|
||||
return arr.length === new Set(arr).size;
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,30 @@
|
||||
import { registerDecorator, ValidationOptions } from 'class-validator';
|
||||
import {
|
||||
registerDecorator,
|
||||
ValidationOptions,
|
||||
ValidatorConstraint,
|
||||
ValidatorConstraintInterface,
|
||||
} from 'class-validator';
|
||||
|
||||
@ValidatorConstraint()
|
||||
export class IsAdminPassword implements ValidatorConstraintInterface {
|
||||
validate(value: string): boolean {
|
||||
// 8文字~64文字でなければ早期に不合格
|
||||
const minLength = 8;
|
||||
const maxLength = 64;
|
||||
if (value.length < minLength || value.length > maxLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 英字の大文字、英字の小文字、アラビア数字、記号(@#$%^&*\-_+=[]{}|\:',.?/`~"();!)から2種類以上組み合わせ
|
||||
const charaTypePattern =
|
||||
/^((?=.*[a-z])(?=.*[A-Z])|(?=.*[a-z])(?=.*[\d])|(?=.*[a-z])(?=.*[@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!])|(?=.*[A-Z])(?=.*[\d])|(?=.*[A-Z])(?=.*[@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!])|(?=.*[\d])(?=.*[@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!]))[a-zA-Z\d@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!]/;
|
||||
return new RegExp(charaTypePattern).test(value);
|
||||
}
|
||||
defaultMessage(): string {
|
||||
return 'Admin password rule not satisfied';
|
||||
}
|
||||
}
|
||||
|
||||
// TODO タスク 2502: バリデータをクラスを使用した記述に統一するで修正する
|
||||
export const IsAdminPasswordvalid = (validationOptions?: ValidationOptions) => {
|
||||
return (object: any, propertyName: string) => {
|
||||
registerDecorator({
|
||||
@ -9,24 +33,7 @@ export const IsAdminPasswordvalid = (validationOptions?: ValidationOptions) => {
|
||||
propertyName: propertyName,
|
||||
constraints: [],
|
||||
options: validationOptions,
|
||||
validator: {
|
||||
validate: (value: string) => {
|
||||
// 8文字~64文字でなければ早期に不合格
|
||||
const minLength = 8;
|
||||
const maxLength = 64;
|
||||
if (value.length < minLength || value.length > maxLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 英字の大文字、英字の小文字、アラビア数字、記号(@#$%^&*\-_+=[]{}|\:',.?/`~"();!)から2種類以上組み合わせ
|
||||
const charaTypePattern =
|
||||
/^((?=.*[a-z])(?=.*[A-Z])|(?=.*[a-z])(?=.*[\d])|(?=.*[a-z])(?=.*[@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!])|(?=.*[A-Z])(?=.*[\d])|(?=.*[A-Z])(?=.*[@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!])|(?=.*[\d])(?=.*[@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!]))[a-zA-Z\d@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!]/;
|
||||
return new RegExp(charaTypePattern).test(value);
|
||||
},
|
||||
defaultMessage: () => {
|
||||
return 'Admin password rule not satisfied';
|
||||
},
|
||||
},
|
||||
validator: IsAdminPassword,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,10 +1,33 @@
|
||||
import {
|
||||
registerDecorator,
|
||||
ValidationOptions,
|
||||
ValidationArguments,
|
||||
ValidatorConstraint,
|
||||
ValidatorConstraintInterface,
|
||||
} from 'class-validator';
|
||||
import { Assignee } from '../../features/tasks/types/types';
|
||||
// TODO タスク 2502: バリデータをクラスを使用した記述に統一するで修正する
|
||||
|
||||
@ValidatorConstraint()
|
||||
export class IsTypist implements ValidatorConstraintInterface {
|
||||
validate(values: Assignee[]): boolean {
|
||||
return values.every((value) => {
|
||||
const { typistUserId, typistGroupId, typistName } = value;
|
||||
if (typistUserId === undefined && typistGroupId === undefined) {
|
||||
return false;
|
||||
}
|
||||
if (typistUserId !== undefined && typistGroupId !== undefined) {
|
||||
return false;
|
||||
}
|
||||
if (!typistName) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
defaultMessage(): string {
|
||||
return 'Request body is invalid format';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validations options
|
||||
* @param [validationOptions]
|
||||
@ -17,28 +40,7 @@ export const IsAssignees = (validationOptions?: ValidationOptions) => {
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
options: validationOptions,
|
||||
validator: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
validate: (values: Assignee[], args: ValidationArguments) => {
|
||||
return values.every((value) => {
|
||||
const { typistUserId, typistGroupId, typistName } = value;
|
||||
if (typistUserId === undefined && typistGroupId === undefined) {
|
||||
return false;
|
||||
}
|
||||
if (typistUserId !== undefined && typistGroupId !== undefined) {
|
||||
return false;
|
||||
}
|
||||
if (!typistName) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
defaultMessage: (args?: ValidationArguments): string => {
|
||||
return 'Request body is invalid format';
|
||||
},
|
||||
},
|
||||
validator: IsTypist,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@ -2,9 +2,46 @@ import {
|
||||
registerDecorator,
|
||||
ValidationArguments,
|
||||
ValidationOptions,
|
||||
ValidatorConstraint,
|
||||
ValidatorConstraintInterface,
|
||||
} from 'class-validator';
|
||||
import { SignupRequest } from '../../features/users/types/types';
|
||||
// TODO タスク 2502: バリデータをクラスを使用した記述に統一するで修正する
|
||||
|
||||
@ValidatorConstraint()
|
||||
export class IsPassword implements ValidatorConstraintInterface {
|
||||
validate(value: string | undefined): boolean {
|
||||
// passwordが設定されていない場合はチェックしない
|
||||
if (value === undefined) {
|
||||
return true;
|
||||
}
|
||||
// 正規表現でパスワードのチェックを行う
|
||||
// 4~16文字の半角英数字と記号のみ
|
||||
const regex = /^[!-~]{4,16}$/;
|
||||
if (!regex.test(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
defaultMessage(): string {
|
||||
return 'EncryptionPassword rule not satisfied';
|
||||
}
|
||||
}
|
||||
|
||||
@ValidatorConstraint()
|
||||
export class IsEncryptionPassword implements ValidatorConstraintInterface {
|
||||
validate(value: string | undefined, args: ValidationArguments): boolean {
|
||||
const { encryption } = args.object as SignupRequest;
|
||||
if (encryption === true && !value) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
defaultMessage(): string {
|
||||
return 'Encryption password is required when encryption is enabled';
|
||||
}
|
||||
}
|
||||
|
||||
export const IsPasswordvalid = (validationOptions?: ValidationOptions) => {
|
||||
return (object: any, propertyName: string) => {
|
||||
registerDecorator({
|
||||
@ -13,29 +50,10 @@ export const IsPasswordvalid = (validationOptions?: ValidationOptions) => {
|
||||
propertyName: propertyName,
|
||||
constraints: [],
|
||||
options: validationOptions,
|
||||
validator: {
|
||||
validate: (value: string | undefined) => {
|
||||
// passwordが設定されていない場合はチェックしない
|
||||
if (value === undefined) {
|
||||
return true;
|
||||
}
|
||||
// 正規表現でパスワードのチェックを行う
|
||||
// 4~16文字の半角英数字と記号のみ
|
||||
const regex = /^[!-~]{4,16}$/;
|
||||
if (!regex.test(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
defaultMessage: () => {
|
||||
return 'EncryptionPassword rule not satisfied';
|
||||
},
|
||||
},
|
||||
validator: IsPassword,
|
||||
});
|
||||
};
|
||||
};
|
||||
// TODO タスク 2502: バリデータをクラスを使用した記述に統一するで修正する
|
||||
export const IsEncryptionPasswordPresent = (
|
||||
validationOptions?: ValidationOptions,
|
||||
) => {
|
||||
@ -46,18 +64,7 @@ export const IsEncryptionPasswordPresent = (
|
||||
propertyName: propertyName,
|
||||
constraints: [],
|
||||
options: validationOptions,
|
||||
validator: {
|
||||
validate: (value: string | undefined, args: ValidationArguments) => {
|
||||
const { encryption } = args.object as SignupRequest;
|
||||
if (encryption === true && !value) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
defaultMessage: () => {
|
||||
return 'Encryption password is required when encryption is enabled';
|
||||
},
|
||||
},
|
||||
validator: IsEncryptionPassword,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@ -2,6 +2,8 @@ import {
|
||||
registerDecorator,
|
||||
ValidationArguments,
|
||||
ValidationOptions,
|
||||
ValidatorConstraint,
|
||||
ValidatorConstraintInterface,
|
||||
} from 'class-validator';
|
||||
import {
|
||||
PostUpdateUserRequest,
|
||||
@ -9,7 +11,26 @@ import {
|
||||
} from '../../features/users/types/types';
|
||||
import { USER_ROLES } from '../../constants';
|
||||
|
||||
// TODO タスク 2502: バリデータをクラスを使用した記述に統一するで修正する
|
||||
@ValidatorConstraint()
|
||||
export class IsRoleAuthorData implements ValidatorConstraintInterface {
|
||||
propertyName: string;
|
||||
constructor(propertyName: string) {
|
||||
this.propertyName = propertyName;
|
||||
}
|
||||
|
||||
validate(value: any, args: ValidationArguments): boolean {
|
||||
const request = args.object as SignupRequest | PostUpdateUserRequest;
|
||||
const { role } = request;
|
||||
if (role === USER_ROLES.AUTHOR && value === undefined) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
defaultMessage(): string {
|
||||
return `When role is author, ${this.propertyName} cannot be undefined`;
|
||||
}
|
||||
}
|
||||
|
||||
export const IsRoleAuthorDataValid = <
|
||||
T extends SignupRequest | PostUpdateUserRequest,
|
||||
>(
|
||||
@ -22,19 +43,7 @@ export const IsRoleAuthorDataValid = <
|
||||
propertyName: propertyName,
|
||||
constraints: [],
|
||||
options: validationOptions,
|
||||
validator: {
|
||||
validate: (value: any, args: ValidationArguments) => {
|
||||
const request = args.object as T;
|
||||
const { role } = request;
|
||||
if (role === USER_ROLES.AUTHOR && value === undefined) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
defaultMessage: () => {
|
||||
return `When role is author, ${propertyName} cannot be undefined`;
|
||||
},
|
||||
},
|
||||
validator: new IsRoleAuthorData(propertyName),
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@ -10,8 +10,7 @@ import { TASK_STATUS } from '../../constants';
|
||||
@ValidatorConstraint()
|
||||
export class IsStatusConstraint implements ValidatorConstraintInterface {
|
||||
private readonly STATUS: string[] = Object.values(TASK_STATUS);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
validate(value: string, args: ValidationArguments): boolean {
|
||||
validate(value: string): boolean {
|
||||
if (value) {
|
||||
// ,で分割した文字列のすべてがTASK_STATUSのプロパティに存在する値であった場合のみtrue
|
||||
return value.split(',').every((state) => this.STATUS.includes(state));
|
||||
@ -20,8 +19,7 @@ export class IsStatusConstraint implements ValidatorConstraintInterface {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
defaultMessage(validationArguments?: ValidationArguments): string {
|
||||
defaultMessage(): string {
|
||||
return `invalid status string`;
|
||||
}
|
||||
}
|
||||
|
||||
50
dictation_server/src/common/validators/worktype.validator.ts
Normal file
50
dictation_server/src/common/validators/worktype.validator.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import {
|
||||
ValidatorConstraint,
|
||||
ValidatorConstraintInterface,
|
||||
ValidationArguments,
|
||||
ValidationOptions,
|
||||
registerDecorator,
|
||||
} from 'class-validator';
|
||||
|
||||
@ValidatorConstraint()
|
||||
export class IsWorktypeIdCharacters implements ValidatorConstraintInterface {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
validate(value: string) {
|
||||
// 正規表現でWorktypeIDのチェックを行う
|
||||
// 以下の禁則文字を除く半角英数記号
|
||||
// \ (backslash)
|
||||
// / (forward slash)
|
||||
// : (colon)
|
||||
// * (asterisk)
|
||||
// ? (question mark)
|
||||
// " (double quotation mark)
|
||||
// < (less-than symbol)
|
||||
// > (greater-than symbol)
|
||||
// | (vertical bar)
|
||||
// . (period)
|
||||
const regex =
|
||||
/^(?!.*\\)(?!.*\/)(?!.*:)(?!.*\*)(?!.*\?)(?!.*")(?!.*<)(?!.*>)(?!.*\|)(?!.*\.)[ -~]+$/;
|
||||
return regex.test(value);
|
||||
}
|
||||
|
||||
defaultMessage(args: ValidationArguments) {
|
||||
return `WorktypeID rule not satisfied`;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* WorktypeIDで使用できる文字列かをチェックする
|
||||
* @param [validationOptions]
|
||||
* @returns
|
||||
*/
|
||||
export function IsWorktypeId(validationOptions?: ValidationOptions) {
|
||||
return function (object: object, propertyName: string) {
|
||||
registerDecorator({
|
||||
name: 'IsWorktypeId',
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
constraints: [],
|
||||
options: validationOptions,
|
||||
validator: IsWorktypeIdCharacters,
|
||||
});
|
||||
};
|
||||
}
|
||||
@ -223,3 +223,18 @@ export const TRIAL_LICENSE_EXPIRATION_DAYS = 30;
|
||||
* @const {number}
|
||||
*/
|
||||
export const TRIAL_LICENSE_ISSUE_NUM = 100;
|
||||
|
||||
/**
|
||||
* worktypeの最大登録数
|
||||
* @const {number}
|
||||
*/
|
||||
export const WORKTYPE_MAX_COUNT = 20;
|
||||
|
||||
/**
|
||||
* worktypeのDefault値の取りうる値
|
||||
**/
|
||||
export const OPTION_ITEM_VALUE_TYPE = {
|
||||
DEFAULT: 'Default',
|
||||
BLANK: 'Blank',
|
||||
LAST_INPUT: 'LastInput',
|
||||
} as const;
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
Req,
|
||||
UseGuards,
|
||||
Param,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiOperation,
|
||||
@ -45,6 +46,11 @@ import {
|
||||
GetWorktypesResponse,
|
||||
CreateWorktypeResponse,
|
||||
CreateWorktypesRequest,
|
||||
GetPartnersRequest,
|
||||
GetPartnersResponse,
|
||||
UpdateWorktypeRequestParam,
|
||||
UpdateWorktypeResponse,
|
||||
UpdateWorktypesRequest,
|
||||
} from './types/types';
|
||||
import { USER_ROLES, ADMIN_ROLES, TIERS } from '../../constants';
|
||||
import { AuthGuard } from '../../common/guards/auth/authguards';
|
||||
@ -633,12 +639,12 @@ export class AccountsController {
|
||||
|
||||
const context = makeContext(payload.userId);
|
||||
|
||||
// TODO: 発行キャンセル処理(仮)。API実装のタスク(2498)で本実装
|
||||
// await this.accountService.cancelIssue(
|
||||
// context,
|
||||
// body.poNumber,
|
||||
// body.orderedAccountId,
|
||||
// );
|
||||
await this.accountService.cancelIssue(
|
||||
context,
|
||||
payload.userId,
|
||||
body.poNumber,
|
||||
body.orderedAccountId,
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -706,10 +712,142 @@ export class AccountsController {
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
console.log(context.trackingId);
|
||||
console.log(worktypeId);
|
||||
console.log(description);
|
||||
await this.accountService.createWorktype(
|
||||
context,
|
||||
userId,
|
||||
worktypeId,
|
||||
description,
|
||||
);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@Post('/worktypes/:id')
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: UpdateWorktypeResponse,
|
||||
description: '成功時のレスポンス',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.BAD_REQUEST,
|
||||
description: 'WorktypeIDが重複 / WorktypeIDが空',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.UNAUTHORIZED,
|
||||
description: '認証エラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
description: '想定外のサーバーエラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiOperation({ operationId: 'updateWorktype' })
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
|
||||
async updateWorktype(
|
||||
@Req() req: Request,
|
||||
@Param() param: UpdateWorktypeRequestParam,
|
||||
@Body() body: UpdateWorktypesRequest,
|
||||
): Promise<UpdateWorktypeResponse> {
|
||||
const { worktypeId, description } = body;
|
||||
const { id } = param;
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
await this.accountService.updateWorktype(
|
||||
context,
|
||||
userId,
|
||||
id,
|
||||
worktypeId,
|
||||
description,
|
||||
);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@Get('/partners')
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: GetPartnersResponse,
|
||||
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: 'getPartners' })
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(
|
||||
RoleGuard.requireds({
|
||||
roles: [ADMIN_ROLES.ADMIN],
|
||||
tiers: [TIERS.TIER1, TIERS.TIER2, TIERS.TIER3, TIERS.TIER4],
|
||||
}),
|
||||
)
|
||||
async getPartners(
|
||||
@Req() req: Request,
|
||||
@Query() query: GetPartnersRequest,
|
||||
): Promise<GetPartnersResponse> {
|
||||
const { limit, offset } = query;
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
// TODO: パートナー取得APIで実装
|
||||
// await this.accountService.getPartners(
|
||||
// context,
|
||||
// body.limit,
|
||||
// body.offset,
|
||||
// );
|
||||
|
||||
// 仮のreturn
|
||||
return {
|
||||
total: 1,
|
||||
partners: [
|
||||
{
|
||||
name: 'testA',
|
||||
tier: 5,
|
||||
accountId: 1,
|
||||
country: 'US',
|
||||
primaryAdmin: 'nameA',
|
||||
email: 'aaa@example.com',
|
||||
dealerManagement: true,
|
||||
},
|
||||
{
|
||||
name: 'testB',
|
||||
tier: 5,
|
||||
accountId: 2,
|
||||
country: 'US',
|
||||
primaryAdmin: 'nameB',
|
||||
email: 'bbb@example.com',
|
||||
dealerManagement: false,
|
||||
},
|
||||
{
|
||||
name: 'testC',
|
||||
tier: 5,
|
||||
accountId: 1,
|
||||
country: 'US',
|
||||
primaryAdmin: 'nothing',
|
||||
email: 'nothing',
|
||||
dealerManagement: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ import {
|
||||
createLicenseOrder,
|
||||
createLicenseSetExpiryDateAndStatus,
|
||||
createWorktype,
|
||||
getOptionItems,
|
||||
getSortCriteria,
|
||||
getTypistGroup,
|
||||
getTypistGroupMember,
|
||||
@ -31,10 +32,19 @@ import {
|
||||
getUserFromExternalId,
|
||||
getUsers,
|
||||
makeTestUser,
|
||||
makeHierarchicalAccounts,
|
||||
} from '../../common/test/utility';
|
||||
import { AccountsService } from './accounts.service';
|
||||
import { Context, makeContext } from '../../common/log';
|
||||
import { TIERS, USER_ROLES } from '../../constants';
|
||||
import {
|
||||
LICENSE_ALLOCATED_STATUS,
|
||||
LICENSE_ISSUE_STATUS,
|
||||
LICENSE_TYPE,
|
||||
OPTION_ITEM_VALUE_TYPE,
|
||||
TIERS,
|
||||
USER_ROLES,
|
||||
WORKTYPE_MAX_COUNT,
|
||||
} from '../../constants';
|
||||
import { License } from '../../repositories/licenses/entity/license.entity';
|
||||
import {
|
||||
overrideAccountsRepositoryService,
|
||||
@ -45,7 +55,13 @@ import {
|
||||
import { AdB2cService } from '../../gateways/adb2c/adb2c.service';
|
||||
import { BlobstorageService } from '../../gateways/blobstorage/blobstorage.service';
|
||||
import { UserGroupsRepositoryService } from '../../repositories/user_groups/user_groups.repository.service';
|
||||
import {
|
||||
createOrder,
|
||||
selectLicense,
|
||||
selectOrderLicense,
|
||||
} from '../licenses/test/utility';
|
||||
import { WorktypesRepositoryService } from '../../repositories/worktypes/worktypes.repository.service';
|
||||
import { Worktype } from '../../repositories/worktypes/entity/worktype.entity';
|
||||
|
||||
describe('createAccount', () => {
|
||||
let source: DataSource = null;
|
||||
@ -1820,14 +1836,79 @@ describe('getPartnerAccount', () => {
|
||||
).account;
|
||||
|
||||
// 所有ライセンスを追加(親:3、子1:1、子2:2)
|
||||
await createLicense(source, parentAccountId);
|
||||
await createLicense(source, parentAccountId);
|
||||
await createLicense(source, parentAccountId);
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
null,
|
||||
parentAccountId,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
1,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
2,
|
||||
null,
|
||||
parentAccountId,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
1,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
3,
|
||||
null,
|
||||
parentAccountId,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
1,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
4,
|
||||
null,
|
||||
childAccountId1,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
2,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
await createLicense(source, childAccountId1);
|
||||
|
||||
await createLicense(source, childAccountId2);
|
||||
await createLicense(source, childAccountId2);
|
||||
await createLicense(
|
||||
source,
|
||||
5,
|
||||
null,
|
||||
childAccountId2,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
3,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
6,
|
||||
null,
|
||||
childAccountId2,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
3,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
// ライセンス注文を追加(子1→親:10ライセンス、子2→親:5ライセンス)
|
||||
await createLicenseOrder(
|
||||
@ -1973,7 +2054,12 @@ describe('getPartnerAccount', () => {
|
||||
}
|
||||
|
||||
// 有効期限未設定のライセンスを1件追加(子1)
|
||||
await createLicense(source, childAccountId1);
|
||||
await createLicenseSetExpiryDateAndStatus(
|
||||
source,
|
||||
childAccountId1,
|
||||
null,
|
||||
'Unallocated',
|
||||
);
|
||||
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const accountId = parentAccountId;
|
||||
@ -2165,9 +2251,42 @@ describe('issueLicense', () => {
|
||||
});
|
||||
|
||||
// 親のライセンスを作成する(3個)
|
||||
await createLicense(source, parentAccountId);
|
||||
await createLicense(source, parentAccountId);
|
||||
await createLicense(source, parentAccountId);
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
null,
|
||||
parentAccountId,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
1,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
2,
|
||||
null,
|
||||
parentAccountId,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
1,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
3,
|
||||
null,
|
||||
parentAccountId,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
1,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
// 子から親への注文を作成する(2個)
|
||||
await createLicenseOrder(
|
||||
source,
|
||||
@ -2224,9 +2343,42 @@ describe('issueLicense', () => {
|
||||
role: 'admin',
|
||||
});
|
||||
// 親のライセンスを作成する(3個)
|
||||
await createLicense(source, parentAccountId);
|
||||
await createLicense(source, parentAccountId);
|
||||
await createLicense(source, parentAccountId);
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
null,
|
||||
parentAccountId,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
1,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
2,
|
||||
null,
|
||||
parentAccountId,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
1,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
3,
|
||||
null,
|
||||
parentAccountId,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
1,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
// 子から親への注文を作成する(2個)
|
||||
await createLicenseOrder(
|
||||
source,
|
||||
@ -2282,9 +2434,42 @@ describe('issueLicense', () => {
|
||||
});
|
||||
|
||||
// 親のライセンスを作成する(3個)
|
||||
await createLicense(source, parentAccountId);
|
||||
await createLicense(source, parentAccountId);
|
||||
await createLicense(source, parentAccountId);
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
null,
|
||||
parentAccountId,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
1,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
2,
|
||||
null,
|
||||
parentAccountId,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
1,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
3,
|
||||
null,
|
||||
parentAccountId,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
1,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
// 子から親への注文を作成する(4個)
|
||||
await createLicenseOrder(
|
||||
source,
|
||||
@ -3263,3 +3448,710 @@ describe('getWorktypes', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('createWorktype', () => {
|
||||
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('Worktypeを作成できる', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
// Worktypeが未登録であることを確認
|
||||
{
|
||||
const worktypes = await getWorktypes(source, account.id);
|
||||
const optionItems = await getOptionItems(source);
|
||||
expect(worktypes.length).toBe(0);
|
||||
expect(optionItems.length).toBe(0);
|
||||
}
|
||||
|
||||
await service.createWorktype(
|
||||
context,
|
||||
admin.external_id,
|
||||
'worktype1',
|
||||
'description1',
|
||||
);
|
||||
|
||||
//実行結果を確認
|
||||
{
|
||||
const worktypes = await getWorktypes(source, account.id);
|
||||
const optionItems = await getOptionItems(source, worktypes[0].id);
|
||||
expect(worktypes.length).toBe(1);
|
||||
expect(worktypes[0].custom_worktype_id).toBe('worktype1');
|
||||
expect(worktypes[0].description).toBe('description1');
|
||||
expect(optionItems.length).toBe(10);
|
||||
expect(optionItems[0].item_label).toBe('');
|
||||
expect(optionItems[0].default_value_type).toBe(
|
||||
OPTION_ITEM_VALUE_TYPE.DEFAULT,
|
||||
);
|
||||
expect(optionItems[0].initial_value).toBe('');
|
||||
}
|
||||
});
|
||||
|
||||
it('WorktypeIDが登録済みのWorktypeIDと重複した場合、400エラーとなること', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const context = makeContext(admin.external_id);
|
||||
const worktypeId = 'worktype1';
|
||||
await createWorktype(source, account.id, worktypeId);
|
||||
|
||||
//作成したデータを確認
|
||||
{
|
||||
const worktypes = await getWorktypes(source, account.id);
|
||||
expect(worktypes.length).toBe(1);
|
||||
expect(worktypes[0].custom_worktype_id).toBe(worktypeId);
|
||||
}
|
||||
|
||||
try {
|
||||
await service.createWorktype(context, admin.external_id, worktypeId);
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E011001'));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('WorktypeIDがすでに最大登録数(20件)まで登録されている場合、400エラーとなること', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
// あらかじめ最大登録数分のWorktypeを登録する
|
||||
for (let i = 0; i < WORKTYPE_MAX_COUNT; i++) {
|
||||
await createWorktype(source, account.id, `worktype${i + 1}`);
|
||||
}
|
||||
|
||||
//作成したデータを確認
|
||||
{
|
||||
const worktypes = await getWorktypes(source, account.id);
|
||||
expect(worktypes.length).toBe(WORKTYPE_MAX_COUNT);
|
||||
}
|
||||
|
||||
try {
|
||||
await service.createWorktype(context, admin.external_id, 'newWorktypeID');
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E011002'));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('DBアクセスに失敗した場合、500エラーを返却する', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
// 第五階層のアカウント作成
|
||||
const { admin } = await makeTestAccount(source, { tier: 5 });
|
||||
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
//DBアクセスに失敗するようにする
|
||||
const worktypeService = module.get<WorktypesRepositoryService>(
|
||||
WorktypesRepositoryService,
|
||||
);
|
||||
worktypeService.createWorktype = jest.fn().mockRejectedValue('DB failed');
|
||||
|
||||
try {
|
||||
await service.createWorktype(context, admin.external_id, 'worktype1');
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateWorktype', () => {
|
||||
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('Worktypeを更新できる', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
const worktype = new Worktype();
|
||||
worktype.custom_worktype_id = 'worktypeID1';
|
||||
worktype.description = 'description1';
|
||||
|
||||
await createWorktype(
|
||||
source,
|
||||
account.id,
|
||||
worktype.custom_worktype_id,
|
||||
worktype.description,
|
||||
);
|
||||
|
||||
// Worktypeを確認
|
||||
const worktypes = await getWorktypes(source, account.id);
|
||||
{
|
||||
expect(worktypes.length).toBe(1);
|
||||
expect(worktypes[0].custom_worktype_id).toBe(worktype.custom_worktype_id);
|
||||
expect(worktypes[0].description).toBe(worktype.description);
|
||||
}
|
||||
|
||||
const updateWorktypeId = 'updateWorktypeID';
|
||||
const updateDescription = 'updateDescription';
|
||||
|
||||
await service.updateWorktype(
|
||||
context,
|
||||
admin.external_id,
|
||||
worktypes[0].id,
|
||||
updateWorktypeId,
|
||||
updateDescription,
|
||||
);
|
||||
|
||||
//実行結果を確認
|
||||
{
|
||||
const worktypes = await getWorktypes(source, account.id);
|
||||
expect(worktypes.length).toBe(1);
|
||||
expect(worktypes[0].custom_worktype_id).toBe(updateWorktypeId);
|
||||
expect(worktypes[0].description).toBe(updateDescription);
|
||||
}
|
||||
});
|
||||
|
||||
it('指定したIDが登録されていない場合、400エラーとなること', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
const worktype = new Worktype();
|
||||
worktype.custom_worktype_id = 'worktypeID1';
|
||||
worktype.description = 'description1';
|
||||
|
||||
await createWorktype(
|
||||
source,
|
||||
account.id,
|
||||
worktype.custom_worktype_id,
|
||||
worktype.description,
|
||||
);
|
||||
|
||||
// Worktypeを確認
|
||||
{
|
||||
const worktypes = await getWorktypes(source, account.id);
|
||||
expect(worktypes.length).toBe(1);
|
||||
expect(worktypes[0].custom_worktype_id).toBe(worktype.custom_worktype_id);
|
||||
expect(worktypes[0].description).toBe(worktype.description);
|
||||
}
|
||||
|
||||
try {
|
||||
await service.updateWorktype(
|
||||
context,
|
||||
admin.external_id,
|
||||
999,
|
||||
'newWorktypeID',
|
||||
'newDescription',
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E011003'));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('WorktypeIDが登録済みのWorktypeIDと重複した場合、400エラーとなること', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const context = makeContext(admin.external_id);
|
||||
const worktype1 = new Worktype();
|
||||
worktype1.custom_worktype_id = 'worktypeID1';
|
||||
worktype1.description = 'description1';
|
||||
|
||||
const worktype2 = new Worktype();
|
||||
worktype2.custom_worktype_id = 'worktypeID2';
|
||||
worktype2.description = 'description2';
|
||||
|
||||
await createWorktype(
|
||||
source,
|
||||
account.id,
|
||||
worktype1.custom_worktype_id,
|
||||
worktype1.description,
|
||||
);
|
||||
await createWorktype(
|
||||
source,
|
||||
account.id,
|
||||
worktype2.custom_worktype_id,
|
||||
worktype2.description,
|
||||
);
|
||||
|
||||
//作成したデータを確認
|
||||
const worktypes = await getWorktypes(source, account.id);
|
||||
{
|
||||
expect(worktypes.length).toBe(2);
|
||||
expect(worktypes[0].custom_worktype_id).toBe(
|
||||
worktype1.custom_worktype_id,
|
||||
);
|
||||
expect(worktypes[0].description).toBe(worktype1.description);
|
||||
expect(worktypes[1].custom_worktype_id).toBe(
|
||||
worktype2.custom_worktype_id,
|
||||
);
|
||||
expect(worktypes[1].description).toBe(worktype2.description);
|
||||
}
|
||||
|
||||
try {
|
||||
await service.updateWorktype(
|
||||
context,
|
||||
admin.external_id,
|
||||
worktypes[0].id,
|
||||
worktype2.custom_worktype_id,
|
||||
worktype2.description,
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E011001'));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('WorktypeIDが登録済みの指定IDのWorktypeIDと重複した場合でも更新できること', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
const worktype = new Worktype();
|
||||
worktype.custom_worktype_id = 'worktypeID1';
|
||||
worktype.description = 'description1';
|
||||
|
||||
await createWorktype(
|
||||
source,
|
||||
account.id,
|
||||
worktype.custom_worktype_id,
|
||||
worktype.description,
|
||||
);
|
||||
|
||||
// Worktypeを確認
|
||||
const worktypes = await getWorktypes(source, account.id);
|
||||
{
|
||||
expect(worktypes.length).toBe(1);
|
||||
expect(worktypes[0].custom_worktype_id).toBe(worktype.custom_worktype_id);
|
||||
expect(worktypes[0].description).toBe(worktype.description);
|
||||
}
|
||||
|
||||
const updateDescription = 'updateDescription';
|
||||
|
||||
await service.updateWorktype(
|
||||
context,
|
||||
admin.external_id,
|
||||
worktypes[0].id,
|
||||
worktype.custom_worktype_id,
|
||||
updateDescription,
|
||||
);
|
||||
|
||||
//実行結果を確認
|
||||
{
|
||||
const worktypes = await getWorktypes(source, account.id);
|
||||
expect(worktypes.length).toBe(1);
|
||||
expect(worktypes[0].custom_worktype_id).toBe(worktype.custom_worktype_id);
|
||||
expect(worktypes[0].description).toBe(updateDescription);
|
||||
}
|
||||
});
|
||||
|
||||
it('DBアクセスに失敗した場合、500エラーを返却する', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
const worktype = new Worktype();
|
||||
worktype.custom_worktype_id = 'worktypeID1';
|
||||
worktype.description = 'description1';
|
||||
|
||||
await createWorktype(
|
||||
source,
|
||||
account.id,
|
||||
worktype.custom_worktype_id,
|
||||
worktype.description,
|
||||
);
|
||||
|
||||
// Worktypeを確認
|
||||
const worktypes = await getWorktypes(source, account.id);
|
||||
{
|
||||
expect(worktypes.length).toBe(1);
|
||||
expect(worktypes[0].custom_worktype_id).toBe(worktype.custom_worktype_id);
|
||||
expect(worktypes[0].description).toBe(worktype.description);
|
||||
}
|
||||
|
||||
//DBアクセスに失敗するようにする
|
||||
const worktypeService = module.get<WorktypesRepositoryService>(
|
||||
WorktypesRepositoryService,
|
||||
);
|
||||
worktypeService.updateWorktype = jest.fn().mockRejectedValue('DB failed');
|
||||
|
||||
try {
|
||||
await service.updateWorktype(
|
||||
context,
|
||||
admin.external_id,
|
||||
worktypes[0].id,
|
||||
'newWorktype',
|
||||
'newDescription',
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('ライセンス発行キャンセル', () => {
|
||||
let source: DataSource = null;
|
||||
beforeEach(async () => {
|
||||
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 { tier1Accounts: tier1Accounts, tier4Accounts: tier4Accounts } =
|
||||
await makeHierarchicalAccounts(source);
|
||||
const tier5Accounts = await makeTestAccount(source, {
|
||||
parent_account_id: tier4Accounts[0].account.id,
|
||||
tier: 5,
|
||||
});
|
||||
const poNumber = 'CANCEL_TEST';
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - 10);
|
||||
await createOrder(
|
||||
source,
|
||||
poNumber,
|
||||
tier5Accounts.account.id,
|
||||
tier5Accounts.account.parent_account_id,
|
||||
date,
|
||||
1,
|
||||
LICENSE_ISSUE_STATUS.ISSUED,
|
||||
);
|
||||
date.setDate(date.getDate() + 10);
|
||||
// 発行時に論理削除されたライセンス情報
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
date,
|
||||
tier5Accounts.account.id,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
date,
|
||||
1,
|
||||
);
|
||||
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
await service.cancelIssue(
|
||||
makeContext('trackingId'),
|
||||
tier1Accounts[0].users[0].external_id,
|
||||
poNumber,
|
||||
tier5Accounts.account.id,
|
||||
);
|
||||
|
||||
// 発行待ちに戻した注文の状態確認
|
||||
const orderRecord = await selectOrderLicense(
|
||||
source,
|
||||
tier5Accounts.account.id,
|
||||
poNumber,
|
||||
);
|
||||
expect(orderRecord.orderLicense.issued_at).toBe(null);
|
||||
expect(orderRecord.orderLicense.status).toBe(
|
||||
LICENSE_ISSUE_STATUS.ISSUE_REQUESTING,
|
||||
);
|
||||
// 未割当に戻したライセンスの状態確認
|
||||
const licenseRecord = await selectLicense(source, 1);
|
||||
expect(licenseRecord.license.status).toBe(
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
);
|
||||
expect(licenseRecord.license.delete_order_id).toBe(null);
|
||||
expect(licenseRecord.license.deleted_at).toBe(null);
|
||||
});
|
||||
it('ライセンス発行のキャンセルが完了する(第二階層で実行)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const { tier2Accounts: tier2Accounts, tier4Accounts: tier4Accounts } =
|
||||
await makeHierarchicalAccounts(source);
|
||||
const tier5Accounts = await makeTestAccount(source, {
|
||||
parent_account_id: tier4Accounts[0].account.id,
|
||||
tier: 5,
|
||||
});
|
||||
const poNumber = 'CANCEL_TEST';
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - 10);
|
||||
await createOrder(
|
||||
source,
|
||||
poNumber,
|
||||
tier5Accounts.account.id,
|
||||
tier5Accounts.account.parent_account_id,
|
||||
date,
|
||||
1,
|
||||
LICENSE_ISSUE_STATUS.ISSUED,
|
||||
);
|
||||
date.setDate(date.getDate() + 10);
|
||||
// 発行時に論理削除されたライセンス情報
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
date,
|
||||
tier5Accounts.account.id,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
date,
|
||||
1,
|
||||
);
|
||||
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
await service.cancelIssue(
|
||||
makeContext('trackingId'),
|
||||
tier2Accounts[0].users[0].external_id,
|
||||
poNumber,
|
||||
tier5Accounts.account.id,
|
||||
);
|
||||
|
||||
// 発行待ちに戻した注文の状態確認
|
||||
const orderRecord = await selectOrderLicense(
|
||||
source,
|
||||
tier5Accounts.account.id,
|
||||
poNumber,
|
||||
);
|
||||
expect(orderRecord.orderLicense.issued_at).toBe(null);
|
||||
expect(orderRecord.orderLicense.status).toBe(
|
||||
LICENSE_ISSUE_STATUS.ISSUE_REQUESTING,
|
||||
);
|
||||
// 未割当に戻したライセンスの状態確認
|
||||
const licenseRecord = await selectLicense(source, 1);
|
||||
expect(licenseRecord.license.status).toBe(
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
);
|
||||
expect(licenseRecord.license.delete_order_id).toBe(null);
|
||||
expect(licenseRecord.license.deleted_at).toBe(null);
|
||||
});
|
||||
it('キャンセル対象の発行が存在しない場合エラー', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const { tier1Accounts: tier1Accounts, tier4Accounts: tier4Accounts } =
|
||||
await makeHierarchicalAccounts(source);
|
||||
const tier5Accounts = await makeTestAccount(source, {
|
||||
parent_account_id: tier4Accounts[0].account.id,
|
||||
tier: 5,
|
||||
});
|
||||
const poNumber = 'CANCEL_TEST';
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
await expect(
|
||||
service.cancelIssue(
|
||||
makeContext('trackingId'),
|
||||
tier1Accounts[0].users[0].external_id,
|
||||
poNumber,
|
||||
tier5Accounts.account.id,
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010809'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
it('キャンセル対象の発行が14日より経過していた場合エラー', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const { tier1Accounts: tier1Accounts, tier4Accounts: tier4Accounts } =
|
||||
await makeHierarchicalAccounts(source);
|
||||
const tier5Accounts = await makeTestAccount(source, {
|
||||
parent_account_id: tier4Accounts[0].account.id,
|
||||
tier: 5,
|
||||
});
|
||||
const poNumber = 'CANCEL_TEST';
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - 15);
|
||||
await createOrder(
|
||||
source,
|
||||
poNumber,
|
||||
tier5Accounts.account.id,
|
||||
tier5Accounts.account.parent_account_id,
|
||||
date,
|
||||
1,
|
||||
LICENSE_ISSUE_STATUS.ISSUED,
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
date,
|
||||
tier5Accounts.account.id,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
1,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
await expect(
|
||||
service.cancelIssue(
|
||||
makeContext('trackingId'),
|
||||
tier1Accounts[0].users[0].external_id,
|
||||
poNumber,
|
||||
tier5Accounts.account.id,
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010810'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
it('キャンセル対象の発行のライセンスが使われていた場合エラー', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const { tier1Accounts: tier1Accounts, tier4Accounts: tier4Accounts } =
|
||||
await makeHierarchicalAccounts(source);
|
||||
const tier5Accounts = await makeTestAccount(source, {
|
||||
parent_account_id: tier4Accounts[0].account.id,
|
||||
tier: 5,
|
||||
});
|
||||
const poNumber = 'CANCEL_TEST';
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - 14);
|
||||
await createOrder(
|
||||
source,
|
||||
poNumber,
|
||||
tier5Accounts.account.id,
|
||||
tier5Accounts.account.parent_account_id,
|
||||
date,
|
||||
1,
|
||||
LICENSE_ISSUE_STATUS.ISSUED,
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
date,
|
||||
tier5Accounts.account.id,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
null,
|
||||
1,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
await expect(
|
||||
service.cancelIssue(
|
||||
makeContext('trackingId'),
|
||||
tier1Accounts[0].users[0].external_id,
|
||||
poNumber,
|
||||
tier5Accounts.account.id,
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010811'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
it('自身のパートナー以外の発行をキャンセルしようとした場合、エラー', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const { tier1Accounts: tier1Accounts } = await makeHierarchicalAccounts(
|
||||
source,
|
||||
);
|
||||
const tier5Accounts = await makeTestAccount(source, {
|
||||
parent_account_id: 100,
|
||||
tier: 5,
|
||||
});
|
||||
const poNumber = 'CANCEL_TEST';
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - 14);
|
||||
await createOrder(
|
||||
source,
|
||||
poNumber,
|
||||
tier5Accounts.account.id,
|
||||
tier5Accounts.account.parent_account_id,
|
||||
date,
|
||||
1,
|
||||
LICENSE_ISSUE_STATUS.ISSUED,
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
date,
|
||||
tier5Accounts.account.id,
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
1,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
await expect(
|
||||
service.cancelIssue(
|
||||
makeContext('trackingId'),
|
||||
tier1Accounts[0].users[0].external_id,
|
||||
poNumber,
|
||||
tier5Accounts.account.id,
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E000108'), HttpStatus.UNAUTHORIZED),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -40,6 +40,9 @@ import {
|
||||
LicensesShortageError,
|
||||
AlreadyIssuedError,
|
||||
OrderNotFoundError,
|
||||
AlreadyLicenseStatusChangedError,
|
||||
AlreadyLicenseAllocatedError,
|
||||
CancellationPeriodExpiredError,
|
||||
} from '../../repositories/licenses/errors/types';
|
||||
import { BlobstorageService } from '../../gateways/blobstorage/blobstorage.service';
|
||||
import {
|
||||
@ -47,6 +50,11 @@ import {
|
||||
TypistIdInvalidError,
|
||||
} from '../../repositories/user_groups/errors/types';
|
||||
import { WorktypesRepositoryService } from '../../repositories/worktypes/worktypes.repository.service';
|
||||
import {
|
||||
WorktypeIdAlreadyExistsError,
|
||||
WorktypeIdMaxCountError,
|
||||
WorktypeIdNotFoundError,
|
||||
} from '../../repositories/worktypes/errors/types';
|
||||
|
||||
@Injectable()
|
||||
export class AccountsService {
|
||||
@ -1053,6 +1061,90 @@ export class AccountsService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ライセンス発行をキャンセルする
|
||||
* @param context
|
||||
* @param extarnalId
|
||||
* @param poNumber
|
||||
* @param orderedAccountId
|
||||
*/
|
||||
async cancelIssue(
|
||||
context: Context,
|
||||
extarnalId: string,
|
||||
poNumber: string,
|
||||
orderedAccountId: number,
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.cancelIssue.name} | params: { ` +
|
||||
`extarnalId: ${extarnalId}, ` +
|
||||
`poNumber: ${poNumber}, ` +
|
||||
`orderedAccountId: ${orderedAccountId}, };`,
|
||||
);
|
||||
let myAccountId: number;
|
||||
|
||||
try {
|
||||
// ユーザIDからアカウントIDを取得する
|
||||
myAccountId = (
|
||||
await this.usersRepository.findUserByExternalId(extarnalId)
|
||||
).account_id;
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
switch (e.constructor) {
|
||||
default:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
this.logger.log(`[OUT] [${context.trackingId}] ${this.cancelIssue.name}`);
|
||||
}
|
||||
|
||||
// 注文元アカウントIDの親世代を取得
|
||||
const parentAccountIds = await this.accountRepository.getHierarchyParents(
|
||||
orderedAccountId,
|
||||
);
|
||||
// 自身が存在しない場合、エラー
|
||||
if (!parentAccountIds.includes(myAccountId)) {
|
||||
this.logger.log(`[OUT] [${context.trackingId}] ${this.cancelIssue.name}`);
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000108'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// 発行キャンセル処理
|
||||
await this.accountRepository.cancelIssue(orderedAccountId, poNumber);
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
switch (e.constructor) {
|
||||
case AlreadyLicenseStatusChangedError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010809'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
case CancellationPeriodExpiredError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010810'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
case AlreadyLicenseAllocatedError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010811'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
default:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
this.logger.log(`[OUT] [${context.trackingId}] ${this.cancelIssue.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ワークタイプ一覧を取得します
|
||||
* @param context
|
||||
@ -1091,4 +1183,133 @@ export class AccountsService {
|
||||
);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* ワークタイプを作成します
|
||||
* @param context
|
||||
* @param externalId
|
||||
* @param worktypeId
|
||||
* @param [description]
|
||||
* @returns worktype
|
||||
*/
|
||||
async createWorktype(
|
||||
context: Context,
|
||||
externalId: string,
|
||||
worktypeId: string,
|
||||
description?: string,
|
||||
): Promise<void> {
|
||||
this.logger.log(`[IN] [${context.trackingId}] ${this.createWorktype.name}`);
|
||||
|
||||
try {
|
||||
// 外部IDをもとにユーザー情報を取得する
|
||||
const { account_id: accountId } =
|
||||
await this.usersRepository.findUserByExternalId(externalId);
|
||||
|
||||
await this.worktypesRepository.createWorktype(
|
||||
accountId,
|
||||
worktypeId,
|
||||
description,
|
||||
);
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
if (e instanceof Error) {
|
||||
switch (e.constructor) {
|
||||
// WorktypeIDが既に存在する場合は400エラーを返す
|
||||
case WorktypeIdAlreadyExistsError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E011001'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
// WorktypeIDが登録上限以上の場合は400エラーを返す
|
||||
case WorktypeIdMaxCountError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E011002'),
|
||||
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.createWorktype.name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ワークタイプを更新します
|
||||
* @param context
|
||||
* @param externalId
|
||||
* @param id ワークタイプの内部ID
|
||||
* @param worktypeId ユーザーが設定するワークタイプ名
|
||||
* @param [description]
|
||||
* @returns worktype
|
||||
*/
|
||||
async updateWorktype(
|
||||
context: Context,
|
||||
externalId: string,
|
||||
id: number,
|
||||
worktypeId: string,
|
||||
description?: string,
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.updateWorktype.name} | params: { ` +
|
||||
`externalId: ${externalId}, ` +
|
||||
`id: ${id}, ` +
|
||||
`worktypeId: ${worktypeId}, ` +
|
||||
`description: ${description} };`,
|
||||
);
|
||||
|
||||
try {
|
||||
// 外部IDをもとにユーザー情報を取得する
|
||||
const { account_id: accountId } =
|
||||
await this.usersRepository.findUserByExternalId(externalId);
|
||||
|
||||
// ワークタイプを更新する
|
||||
await this.worktypesRepository.updateWorktype(
|
||||
accountId,
|
||||
id,
|
||||
worktypeId,
|
||||
description,
|
||||
);
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
if (e instanceof Error) {
|
||||
switch (e.constructor) {
|
||||
// ユーザーが設定したWorktypeIDが既存WorktypeのWorktypeIDと重複する場合は400エラーを返す
|
||||
case WorktypeIdAlreadyExistsError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E011001'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
// 内部IDで指定されたWorktypeが存在しない場合は400エラーを返す
|
||||
case WorktypeIdNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E011003'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
default:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.trackingId}] ${this.updateWorktype.name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import { SortCriteria } from '../../../repositories/sort_criteria/entity/sort_cr
|
||||
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';
|
||||
import { OptionItem } from '../../../repositories/option_items/entity/option_item.entity';
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: すべてのソート条件を取得する
|
||||
@ -19,17 +20,26 @@ export const getSortCriteria = async (dataSource: DataSource) => {
|
||||
|
||||
export const createLicense = async (
|
||||
datasource: DataSource,
|
||||
licenseId: number,
|
||||
expiry_date: Date,
|
||||
accountId: number,
|
||||
type: string,
|
||||
status: string,
|
||||
allocated_user_id: number,
|
||||
order_id: number,
|
||||
deleted_at: Date,
|
||||
delete_order_id: number,
|
||||
): Promise<void> => {
|
||||
const { identifiers } = await datasource.getRepository(License).insert({
|
||||
expiry_date: null,
|
||||
id: licenseId,
|
||||
expiry_date: expiry_date,
|
||||
account_id: accountId,
|
||||
type: 'NORMAL',
|
||||
status: 'Unallocated',
|
||||
allocated_user_id: null,
|
||||
order_id: null,
|
||||
deleted_at: null,
|
||||
delete_order_id: null,
|
||||
type: type,
|
||||
status: status,
|
||||
allocated_user_id: allocated_user_id,
|
||||
order_id: order_id,
|
||||
deleted_at: deleted_at,
|
||||
delete_order_id: delete_order_id,
|
||||
created_by: 'test_runner',
|
||||
created_at: new Date(),
|
||||
updated_by: 'updater',
|
||||
@ -142,3 +152,17 @@ export const getWorktypes = async (
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// オプションアイテムを取得する
|
||||
export const getOptionItems = async (
|
||||
datasource: DataSource,
|
||||
worktypeId?: number,
|
||||
): Promise<OptionItem[]> => {
|
||||
return worktypeId
|
||||
? await datasource.getRepository(OptionItem).find({
|
||||
where: {
|
||||
worktype_id: worktypeId,
|
||||
},
|
||||
})
|
||||
: await datasource.getRepository(OptionItem).find();
|
||||
};
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
import { IsAdminPasswordvalid } from '../../../common/validators/admin.validator';
|
||||
import { IsUnique } from '../../../common/validators/IsUnique.validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsWorktypeId } from '../../../common/validators/worktype.validator';
|
||||
|
||||
export class CreateAccountRequest {
|
||||
@ApiProperty()
|
||||
@ -350,9 +351,72 @@ export class GetWorktypesResponse {
|
||||
export class CreateWorktypesRequest {
|
||||
@ApiProperty({ minLength: 1, description: 'WorktypeID' })
|
||||
@MinLength(1)
|
||||
@MaxLength(255)
|
||||
@IsWorktypeId()
|
||||
worktypeId: string;
|
||||
@ApiProperty({ description: 'Worktypeの説明', required: false })
|
||||
@MaxLength(255)
|
||||
@IsOptional()
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export class CreateWorktypeResponse {}
|
||||
|
||||
export class UpdateWorktypesRequest {
|
||||
@ApiProperty({ minLength: 1, description: 'WorktypeID' })
|
||||
@MinLength(1)
|
||||
@MaxLength(255)
|
||||
@IsWorktypeId()
|
||||
worktypeId: string;
|
||||
@ApiProperty({ description: 'Worktypeの説明', required: false })
|
||||
@MaxLength(255)
|
||||
@IsOptional()
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export class UpdateWorktypeResponse {}
|
||||
|
||||
export class UpdateWorktypeRequestParam {
|
||||
@ApiProperty({ description: 'Worktypeの内部ID' })
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
id: number;
|
||||
}
|
||||
|
||||
export class GetPartnersRequest {
|
||||
@ApiProperty({ description: '取得件数' })
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
@Type(() => Number)
|
||||
limit: number;
|
||||
@ApiProperty({ description: '開始位置' })
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
@Type(() => Number)
|
||||
offset: number;
|
||||
}
|
||||
|
||||
export class Partner {
|
||||
@ApiProperty({ description: '会社名' })
|
||||
name: string;
|
||||
@ApiProperty({ description: '階層' })
|
||||
tier: number;
|
||||
@ApiProperty({ description: 'アカウントID' })
|
||||
accountId: number;
|
||||
@ApiProperty({ description: '国' })
|
||||
country: string;
|
||||
@ApiProperty({ description: 'プライマリ管理者' })
|
||||
primaryAdmin: string;
|
||||
@ApiProperty({ description: 'プライマリ管理者メールアドレス' })
|
||||
email: string;
|
||||
@ApiProperty({ description: '代行操作許可' })
|
||||
dealerManagement: boolean;
|
||||
}
|
||||
|
||||
export class GetPartnersResponse {
|
||||
@ApiProperty({ description: '合計件数' })
|
||||
total: number;
|
||||
@ApiProperty({ type: [Partner] })
|
||||
partners: Partner[];
|
||||
}
|
||||
|
||||
@ -27,7 +27,11 @@ import { v4 as uuidv4 } from 'uuid';
|
||||
@ApiTags('auth')
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(private readonly authService: AuthService) {}
|
||||
constructor(
|
||||
// TODO「タスク 1828: IDトークンを一度しか使えないようにする」で使用する予定
|
||||
// private readonly redisService: RedisService,
|
||||
private readonly authService: AuthService,
|
||||
) {}
|
||||
|
||||
@Post('token')
|
||||
@ApiResponse({
|
||||
|
||||
@ -4,7 +4,6 @@ import { AdB2cModule } from '../../gateways/adb2c/adb2c.module';
|
||||
import { UsersRepositoryModule } from '../../repositories/users/users.repository.module';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule, AdB2cModule, UsersRepositoryModule],
|
||||
controllers: [AuthController],
|
||||
|
||||
@ -346,6 +346,9 @@ describe('DBテスト', () => {
|
||||
LICENSE_TYPE.CARD,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createCardLicense(source, license_id, issueId, cardLicenseKey);
|
||||
await createCardLicenseIssue(source, issueId);
|
||||
@ -386,6 +389,9 @@ describe('DBテスト', () => {
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
// 2件目、expiry_dateがnull(OneYear)
|
||||
await createLicense(
|
||||
@ -396,6 +402,9 @@ describe('DBテスト', () => {
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
// 3件目、1件目と同じ有効期限
|
||||
await createLicense(
|
||||
@ -406,6 +415,9 @@ describe('DBテスト', () => {
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
// 4件目、expiry_dateが一番遠いデータ
|
||||
await createLicense(
|
||||
@ -416,6 +428,9 @@ describe('DBテスト', () => {
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
// 5件目、expiry_dateがnull(OneYear)
|
||||
await createLicense(
|
||||
@ -426,6 +441,9 @@ describe('DBテスト', () => {
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
// 6件目、ライセンス状態が割当済
|
||||
await createLicense(
|
||||
@ -436,6 +454,9 @@ describe('DBテスト', () => {
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
// 7件目、ライセンス状態が削除済
|
||||
await createLicense(
|
||||
@ -446,6 +467,9 @@ describe('DBテスト', () => {
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.DELETED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
// 8件目、別アカウントの未割当のライセンス
|
||||
await createLicense(
|
||||
@ -456,6 +480,9 @@ describe('DBテスト', () => {
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
// 9件目、有効期限切れのライセンス
|
||||
await createLicense(
|
||||
@ -466,6 +493,9 @@ describe('DBテスト', () => {
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
const service = module.get<LicensesService>(LicensesService);
|
||||
const context = makeContext('userId');
|
||||
@ -518,6 +548,9 @@ describe('ライセンス割り当て', () => {
|
||||
LICENSE_TYPE.CARD,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createLicenseAllocationHistory(source, 1, userId, 1, 'NONE');
|
||||
|
||||
@ -570,6 +603,9 @@ describe('ライセンス割り当て', () => {
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createLicenseAllocationHistory(source, 1, userId, 1, 'NONE');
|
||||
|
||||
@ -616,6 +652,9 @@ describe('ライセンス割り当て', () => {
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
userId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
@ -625,6 +664,9 @@ describe('ライセンス割り当て', () => {
|
||||
LICENSE_TYPE.CARD,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createLicenseAllocationHistory(source, 1, userId, 1, 'NONE');
|
||||
|
||||
@ -697,6 +739,9 @@ describe('ライセンス割り当て', () => {
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
userId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
@ -706,6 +751,9 @@ describe('ライセンス割り当て', () => {
|
||||
LICENSE_TYPE.CARD,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createLicenseAllocationHistory(source, 1, userId, 1, 'NONE');
|
||||
|
||||
@ -742,6 +790,9 @@ describe('ライセンス割り当て', () => {
|
||||
LICENSE_TYPE.CARD,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
userId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
@ -751,6 +802,9 @@ describe('ライセンス割り当て', () => {
|
||||
LICENSE_TYPE.CARD,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createLicenseAllocationHistory(source, 1, userId, 1, 'CARD');
|
||||
|
||||
@ -787,6 +841,9 @@ describe('ライセンス割り当て', () => {
|
||||
LICENSE_TYPE.TRIAL,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
userId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
@ -796,6 +853,9 @@ describe('ライセンス割り当て', () => {
|
||||
LICENSE_TYPE.CARD,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createLicenseAllocationHistory(source, 1, userId, 1, 'TRIAL');
|
||||
|
||||
@ -832,6 +892,9 @@ describe('ライセンス割り当て', () => {
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
@ -863,6 +926,9 @@ describe('ライセンス割り当て', () => {
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
@ -872,6 +938,9 @@ describe('ライセンス割り当て', () => {
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.DELETED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
@ -927,6 +996,9 @@ describe('ライセンス割り当て解除', () => {
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
userId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createLicenseAllocationHistory(source, 1, userId, 1, 'NONE');
|
||||
|
||||
@ -987,6 +1059,9 @@ describe('ライセンス割り当て解除', () => {
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
2,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
@ -996,6 +1071,9 @@ describe('ライセンス割り当て解除', () => {
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE,
|
||||
userId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
await createLicenseAllocationHistory(source, 1, userId, 1, 'NONE');
|
||||
|
||||
@ -1037,6 +1115,7 @@ describe('ライセンス注文キャンセル', () => {
|
||||
poNumber,
|
||||
tier2Accounts[0].account.id,
|
||||
tier2Accounts[0].account.parent_account_id,
|
||||
null,
|
||||
10,
|
||||
'Issue Requesting',
|
||||
);
|
||||
@ -1046,6 +1125,7 @@ describe('ライセンス注文キャンセル', () => {
|
||||
poNumber,
|
||||
tier2Accounts[0].account.id,
|
||||
tier2Accounts[0].account.parent_account_id,
|
||||
null,
|
||||
10,
|
||||
'Order Canceled',
|
||||
);
|
||||
@ -1078,6 +1158,7 @@ describe('ライセンス注文キャンセル', () => {
|
||||
poNumber,
|
||||
tier2Accounts[0].account.id,
|
||||
tier2Accounts[0].account.parent_account_id,
|
||||
null,
|
||||
10,
|
||||
'Issued',
|
||||
);
|
||||
@ -1106,6 +1187,7 @@ describe('ライセンス注文キャンセル', () => {
|
||||
poNumber,
|
||||
tier2Accounts[0].account.id,
|
||||
tier2Accounts[0].account.parent_account_id,
|
||||
null,
|
||||
10,
|
||||
'Order Canceled',
|
||||
);
|
||||
|
||||
@ -15,6 +15,9 @@ export const createLicense = async (
|
||||
type: string,
|
||||
status: string,
|
||||
allocated_user_id: number,
|
||||
order_id: number,
|
||||
deleted_at: Date,
|
||||
delete_order_id: number,
|
||||
): Promise<void> => {
|
||||
const { identifiers } = await datasource.getRepository(License).insert({
|
||||
id: licenseId,
|
||||
@ -23,9 +26,9 @@ export const createLicense = async (
|
||||
type: type,
|
||||
status: status,
|
||||
allocated_user_id: allocated_user_id,
|
||||
order_id: null,
|
||||
deleted_at: null,
|
||||
delete_order_id: null,
|
||||
order_id: order_id,
|
||||
deleted_at: deleted_at,
|
||||
delete_order_id: delete_order_id,
|
||||
created_by: 'test_runner',
|
||||
created_at: new Date(),
|
||||
updated_by: 'updater',
|
||||
@ -100,6 +103,7 @@ export const createOrder = async (
|
||||
poNumber: string,
|
||||
fromId: number,
|
||||
toId: number,
|
||||
issuedAt: Date,
|
||||
quantity: number,
|
||||
status: string,
|
||||
): Promise<void> => {
|
||||
@ -108,7 +112,7 @@ export const createOrder = async (
|
||||
from_account_id: fromId,
|
||||
to_account_id: toId,
|
||||
ordered_at: new Date(),
|
||||
issued_at: null,
|
||||
issued_at: issuedAt,
|
||||
quantity: quantity,
|
||||
status: status,
|
||||
canceled_at: null,
|
||||
|
||||
@ -20,6 +20,7 @@ import {
|
||||
} from '../../common/types/sort/util';
|
||||
import {
|
||||
LICENSE_ALLOCATED_STATUS,
|
||||
LICENSE_EXPIRATION_THRESHOLD_DAYS,
|
||||
LICENSE_ISSUE_STATUS,
|
||||
TIERS,
|
||||
} from '../../constants';
|
||||
@ -28,6 +29,12 @@ import {
|
||||
PartnerLicenseInfoForRepository,
|
||||
} from '../../features/accounts/types/types';
|
||||
import { AccountNotFoundError } from './errors/types';
|
||||
import {
|
||||
AlreadyLicenseAllocatedError,
|
||||
AlreadyLicenseStatusChangedError,
|
||||
CancellationPeriodExpiredError,
|
||||
} from '../licenses/errors/types';
|
||||
import { DateWithZeroTime } from '../../features/licenses/types/types';
|
||||
|
||||
@Injectable()
|
||||
export class AccountsRepositoryService {
|
||||
@ -572,4 +579,110 @@ export class AccountsRepositoryService {
|
||||
|
||||
return accounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 対象のアカウントIDの親世代のアカウントIDをすべて取得する
|
||||
* 順番は、階層(tier)の下位から上位に向かって格納
|
||||
* @param targetAccountId
|
||||
* @returns accountIds
|
||||
*/
|
||||
async getHierarchyParents(targetAccountId: number): Promise<number[]> {
|
||||
return await this.dataSource.transaction(async (entityManager) => {
|
||||
const accountRepository = entityManager.getRepository(Account);
|
||||
const maxTierDifference = TIERS.TIER5 - TIERS.TIER1;
|
||||
const parentAccountIds = [];
|
||||
|
||||
let currentAccountId = targetAccountId;
|
||||
// システム的な最大の階層差異分、親を参照する
|
||||
for (let i = 0; i < maxTierDifference; i++) {
|
||||
const account = await accountRepository.findOne({
|
||||
where: {
|
||||
id: currentAccountId,
|
||||
},
|
||||
});
|
||||
if (!account) {
|
||||
break;
|
||||
}
|
||||
|
||||
parentAccountIds.push(account.parent_account_id);
|
||||
currentAccountId = account.parent_account_id;
|
||||
}
|
||||
|
||||
return parentAccountIds;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 注文元アカウントIDとPOナンバーに紐づくライセンス発行をキャンセルする
|
||||
* @param orderedAccountId:キャンセルしたい発行の注文元アカウントID
|
||||
* @param poNumber:POナンバー
|
||||
*/
|
||||
async cancelIssue(orderedAccountId: 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: orderedAccountId,
|
||||
po_number: poNumber,
|
||||
status: LICENSE_ISSUE_STATUS.ISSUED,
|
||||
},
|
||||
});
|
||||
|
||||
// キャンセル対象の発行が存在しない場合エラー
|
||||
if (!targetOrder) {
|
||||
throw new AlreadyLicenseStatusChangedError(
|
||||
`Cancel issue is failed. Already license order status changed. fromAccountId: ${orderedAccountId}, poNumber: ${poNumber}`,
|
||||
);
|
||||
}
|
||||
|
||||
// キャンセル可能な日付(発行日から14日経過)かどうかに判定する時刻を取得する
|
||||
const currentDateWithoutTime = new DateWithZeroTime();
|
||||
const issuedDateWithoutTime = new DateWithZeroTime(targetOrder.issued_at);
|
||||
const timeDifference =
|
||||
currentDateWithoutTime.getTime() - issuedDateWithoutTime.getTime();
|
||||
const daysDifference = Math.floor(timeDifference / (1000 * 60 * 60 * 24));
|
||||
// 発行日から14日経過しているかをチェック
|
||||
if (daysDifference > LICENSE_EXPIRATION_THRESHOLD_DAYS) {
|
||||
throw new CancellationPeriodExpiredError(
|
||||
`Cancel issue is failed. Cancellation period expired. fromAccountId: ${orderedAccountId}, poNumber: ${poNumber}`,
|
||||
);
|
||||
}
|
||||
// すでに割り当て済みライセンスを含む注文か確認する
|
||||
const licenseRepo = entityManager.getRepository(License);
|
||||
const allocatedLicense = await licenseRepo.findOne({
|
||||
where: {
|
||||
order_id: targetOrder.id,
|
||||
status: Not(LICENSE_ALLOCATED_STATUS.UNALLOCATED),
|
||||
},
|
||||
});
|
||||
|
||||
// 存在した場合エラー
|
||||
if (allocatedLicense) {
|
||||
throw new AlreadyLicenseAllocatedError(
|
||||
`Cancel issue is failed. Already license allocated. fromAccountId: ${orderedAccountId}, poNumber: ${poNumber}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 更新用の変数に値をコピー
|
||||
const updatedOrder = { ...targetOrder };
|
||||
|
||||
// 注文を発行待ちに戻す
|
||||
updatedOrder.issued_at = null;
|
||||
updatedOrder.status = LICENSE_ISSUE_STATUS.ISSUE_REQUESTING;
|
||||
await orderRepo.save(updatedOrder);
|
||||
// 発行時に削除したライセンスを未割当に戻す
|
||||
await licenseRepo.update(
|
||||
{ delete_order_id: targetOrder.id },
|
||||
{
|
||||
status: LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
deleted_at: null,
|
||||
delete_order_id: null,
|
||||
},
|
||||
);
|
||||
// 発行時に発行されたライセンスを削除する
|
||||
await licenseRepo.delete({ order_id: targetOrder.id });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,3 +24,12 @@ export class LicenseAlreadyDeallocatedError extends Error {}
|
||||
|
||||
// 注文キャンセル失敗エラー
|
||||
export class CancelOrderFailedError extends Error {}
|
||||
|
||||
// ライセンス発行キャンセル不可エラー(ステータスが変えられている場合)
|
||||
export class AlreadyLicenseStatusChangedError extends Error {}
|
||||
|
||||
// ライセンス発行キャンセル不可エラー(発行から一定期間経過した場合)
|
||||
export class CancellationPeriodExpiredError extends Error {}
|
||||
|
||||
// ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合)
|
||||
export class AlreadyLicenseAllocatedError extends Error {}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { DataSource, In, IsNull, MoreThanOrEqual } from 'typeorm';
|
||||
import { DataSource, In } from 'typeorm';
|
||||
import {
|
||||
LicenseOrder,
|
||||
License,
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
import {
|
||||
Entity,
|
||||
Column,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
CreateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
@Entity({ name: 'option_items' })
|
||||
export class OptionItem {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
@Column()
|
||||
worktype_id: number;
|
||||
@Column()
|
||||
item_label: string;
|
||||
@Column()
|
||||
default_value_type: string;
|
||||
@Column()
|
||||
initial_value: string;
|
||||
@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,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { OptionItem } from './entity/option_item.entity';
|
||||
import { OptionItemsRepositoryService } from './option_items.repository.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([OptionItem])],
|
||||
providers: [OptionItemsRepositoryService],
|
||||
exports: [OptionItemsRepositoryService],
|
||||
})
|
||||
export class OptionItemsRepositoryModule {}
|
||||
@ -0,0 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
export class OptionItemsRepositoryService {
|
||||
constructor(private dataSource: DataSource) {}
|
||||
}
|
||||
@ -1 +1,6 @@
|
||||
|
||||
// WorktypeID重複エラー
|
||||
export class WorktypeIdAlreadyExistsError extends Error {}
|
||||
// WorktypeID登録上限エラー
|
||||
export class WorktypeIdMaxCountError extends Error {}
|
||||
// WorktypeID不在エラー
|
||||
export class WorktypeIdNotFoundError extends Error {}
|
||||
|
||||
@ -1,6 +1,17 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { DataSource, Not } from 'typeorm';
|
||||
import { Worktype } from './entity/worktype.entity';
|
||||
import {
|
||||
OPTION_ITEM_NUM,
|
||||
OPTION_ITEM_VALUE_TYPE,
|
||||
WORKTYPE_MAX_COUNT,
|
||||
} from '../../constants';
|
||||
import {
|
||||
WorktypeIdAlreadyExistsError,
|
||||
WorktypeIdMaxCountError,
|
||||
WorktypeIdNotFoundError,
|
||||
} from './errors/types';
|
||||
import { OptionItem } from '../option_items/entity/option_item.entity';
|
||||
|
||||
@Injectable()
|
||||
export class WorktypesRepositoryService {
|
||||
@ -19,4 +30,109 @@ export class WorktypesRepositoryService {
|
||||
return worktypes;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* ワークタイプを作成する
|
||||
* @param accountId
|
||||
* @param worktypeId
|
||||
* @param [description]
|
||||
*/
|
||||
async createWorktype(
|
||||
accountId: number,
|
||||
worktypeId: string,
|
||||
description?: string,
|
||||
): Promise<void> {
|
||||
await this.dataSource.transaction(async (entityManager) => {
|
||||
const worktypeRepo = entityManager.getRepository(Worktype);
|
||||
const optionItemRepo = entityManager.getRepository(OptionItem);
|
||||
|
||||
const duplicatedWorktype = await worktypeRepo.findOne({
|
||||
where: { account_id: accountId, custom_worktype_id: worktypeId },
|
||||
});
|
||||
|
||||
// ワークタイプIDが重複している場合はエラー
|
||||
if (duplicatedWorktype) {
|
||||
throw new WorktypeIdAlreadyExistsError(
|
||||
`WorktypeID is already exists. WorktypeID: ${worktypeId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const worktypeCount = await worktypeRepo.count({
|
||||
where: { account_id: accountId },
|
||||
});
|
||||
|
||||
// ワークタイプの登録数が上限に達している場合はエラー
|
||||
if (worktypeCount >= WORKTYPE_MAX_COUNT) {
|
||||
throw new WorktypeIdMaxCountError(
|
||||
`Number of worktype is exceeded the limit. MAX_COUNT: ${WORKTYPE_MAX_COUNT}, currentCount: ${worktypeCount}`,
|
||||
);
|
||||
}
|
||||
|
||||
// ワークタイプを作成
|
||||
const worktype = await worktypeRepo.save({
|
||||
account_id: accountId,
|
||||
custom_worktype_id: worktypeId,
|
||||
description: description,
|
||||
});
|
||||
|
||||
// ワークタイプに紐づくオプションアイテムを10件作成
|
||||
const newOptionItems = Array.from({ length: OPTION_ITEM_NUM }, () => {
|
||||
const optionItem = new OptionItem();
|
||||
optionItem.worktype_id = worktype.id;
|
||||
optionItem.item_label = '';
|
||||
optionItem.default_value_type = OPTION_ITEM_VALUE_TYPE.DEFAULT;
|
||||
optionItem.initial_value = '';
|
||||
|
||||
return optionItem;
|
||||
});
|
||||
|
||||
await optionItemRepo.save(newOptionItems);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* ワークタイプを更新する
|
||||
* @param accountId
|
||||
* @param id ワークタイプの内部ID
|
||||
* @param worktypeId ユーザーが設定するワークタイプ名
|
||||
* @param [description]
|
||||
*/
|
||||
async updateWorktype(
|
||||
accountId: number,
|
||||
id: number,
|
||||
worktypeId: string,
|
||||
description?: string,
|
||||
): Promise<void> {
|
||||
await this.dataSource.transaction(async (entityManager) => {
|
||||
const worktypeRepo = entityManager.getRepository(Worktype);
|
||||
|
||||
const worktype = await worktypeRepo.findOne({
|
||||
where: { account_id: accountId, id: id },
|
||||
});
|
||||
|
||||
// ワークタイプが存在しない場合はエラー
|
||||
if (!worktype) {
|
||||
throw new WorktypeIdNotFoundError(`Worktype is not found. id: ${id}`);
|
||||
}
|
||||
|
||||
const duplicatedWorktype = await worktypeRepo.findOne({
|
||||
where: {
|
||||
account_id: accountId,
|
||||
custom_worktype_id: worktypeId,
|
||||
id: Not(id),
|
||||
},
|
||||
});
|
||||
|
||||
// ワークタイプIDが重複している場合はエラー
|
||||
if (duplicatedWorktype) {
|
||||
throw new WorktypeIdAlreadyExistsError(
|
||||
`WorktypeID is already exists. WorktypeID: ${worktypeId}`,
|
||||
);
|
||||
}
|
||||
|
||||
// ワークタイプを更新
|
||||
worktype.custom_worktype_id = worktypeId;
|
||||
worktype.description = description;
|
||||
await worktypeRepo.save(worktype);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,10 +26,11 @@ services:
|
||||
cache:
|
||||
image: redis:latest
|
||||
container_name: redis-cache
|
||||
command: [ "redis-server", "--requirepass ${REDIS_PASSWORD}" ]
|
||||
ports:
|
||||
- 8000:8000
|
||||
- 6379:6379
|
||||
expose:
|
||||
- 8000
|
||||
- 6379
|
||||
networks:
|
||||
- network
|
||||
volumes:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user