Compare commits

..

No commits in common. "main" and "release-2024-02-27-1" have entirely different histories.

379 changed files with 3117 additions and 73370 deletions

1
.gitignore vendored
View File

@ -1 +0,0 @@
environment_building_tools/logfile.log

View File

@ -1,312 +0,0 @@
# Pipeline側でKeyVaultやDocker、AppService等に対する操作権限を持ったServiceConenctionを作成しておくこと
# また、環境変数 STATIC_DICTATION_DEPLOYMENT_TOKEN の値として静的WebAppsのデプロイトークンを設定しておくこと
trigger:
branches:
include:
- release-ccb
tags:
include:
- stage-*
jobs:
- job: initialize
displayName: Initialize
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
persistCredentials: true
- script: |
git fetch origin release-ccb:release-ccb
if git merge-base --is-ancestor $(Build.SourceVersion) release-ccb; then
echo "This commit is in the release-ccb branch."
else
echo "This commit is not in the release-ccb branch."
exit 1
fi
displayName: 'タグが付けられたCommitがrelease-ccbブランチに存在するか確認'
- job: backend_test
dependsOn: initialize
condition: succeeded('initialize')
displayName: UnitTest
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: Bash@3
displayName: Bash Script (Test)
inputs:
targetType: inline
workingDirectory: dictation_server/.devcontainer
script: |
docker-compose -f pipeline-docker-compose.yml build
docker-compose -f pipeline-docker-compose.yml up -d
docker-compose exec -T dictation_server sudo npm ci
docker-compose exec -T dictation_server sudo npm run migrate:up:test
docker-compose exec -T dictation_server sudo npm run test
- job: backend_build
dependsOn: backend_test
condition: succeeded('backend_test')
displayName: Build And Push Backend Image
pool:
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: Docker@0
displayName: build
inputs:
azureSubscriptionEndpoint: 'omds-service-connection-stg'
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
dockerFile: DockerfileServerDictation.dockerfile
imageName: odmscloud/staging/dictation:$(Build.SourceVersion)
buildArguments: |
BUILD_VERSION=$(Build.SourceVersion)
- task: Docker@0
displayName: push
inputs:
azureSubscriptionEndpoint: 'omds-service-connection-stg'
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: odmscloud/staging/dictation:$(Build.SourceVersion)
- job: frontend_build_staging
dependsOn: backend_build
condition: succeeded('backend_build')
displayName: Build Frontend Files(staging)
variables:
storageAccountName: saomdspipeline
environment: staging
pool:
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:stg
- 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: 'omds-service-connection-stg'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az storage blob upload \
--auth-mode login \
--account-name $(storageAccountName) \
--container-name $(environment) \
--name $(Build.SourceVersion).zip \
--type block \
--overwrite \
--file $(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip
- job: function_test
dependsOn: frontend_build_staging
condition: succeeded('frontend_build_staging')
displayName: UnitTest
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: Bash@3
displayName: Bash Script (Test)
inputs:
targetType: inline
workingDirectory: dictation_function/.devcontainer
script: |
docker-compose -f pipeline-docker-compose.yml build
docker-compose -f pipeline-docker-compose.yml up -d
docker-compose exec -T dictation_function sudo npm ci
docker-compose exec -T dictation_function sudo npm run test
- job: function_build
dependsOn: function_test
condition: succeeded('function_test')
displayName: Build And Push Function Image
pool:
name: odms-deploy-pipeline
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: Npm@1
displayName: npm ci
inputs:
command: ci
workingDir: dictation_function
verbose: false
- task: Docker@0
displayName: build
inputs:
azureSubscriptionEndpoint: 'omds-service-connection-stg'
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
dockerFile: DockerfileFunctionDictation.dockerfile
imageName: odmscloud/staging/dictation_function:$(Build.SourceVersion)
buildArguments: |
BUILD_VERSION=$(Build.SourceVersion)
- task: Docker@0
displayName: push
inputs:
azureSubscriptionEndpoint: 'omds-service-connection-stg'
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: odmscloud/staging/dictation_function:$(Build.SourceVersion)
- job: backend_deploy
dependsOn: function_build
condition: succeeded('function_build')
displayName: Backend Deploy
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: AzureWebAppContainer@1
inputs:
azureSubscription: 'omds-service-connection-stg'
appName: 'app-odms-dictation-stg'
deployToSlotOrASE: true
resourceGroupName: 'stg-application-rg'
slotName: 'staging'
containers: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/dictation:$(Build.SourceVersion)'
- job: frontend_deploy
dependsOn: backend_deploy
condition: succeeded('backend_deploy')
displayName: Deploy Frontend Files
variables:
storageAccountName: saomdspipeline
environment: staging
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: AzureCLI@2
inputs:
azureSubscription: 'omds-service-connection-stg'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az storage blob download \
--auth-mode login \
--account-name $(storageAccountName) \
--container-name $(environment) \
--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: function_deploy
dependsOn: frontend_deploy
condition: succeeded('frontend_deploy')
displayName: Function Deploy
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: AzureFunctionAppContainer@1
inputs:
azureSubscription: 'omds-service-connection-stg'
appName: 'func-odms-dictation-stg'
imageName: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/dictation_function:$(Build.SourceVersion)'
- job: smoke_test
dependsOn: function_deploy
condition: succeeded('function_deploy')
displayName: 'smoke test'
pool:
name: odms-deploy-pipeline
steps:
- checkout: self
clean: true
fetchDepth: 1
# スモークテスト用にjobを確保
- job: swap_slot
dependsOn: smoke_test
condition: succeeded('smoke_test')
displayName: 'Swap Staging and Production'
pool:
name: odms-deploy-pipeline
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: AzureAppServiceManage@0
displayName: 'Azure App Service Manage: app-odms-dictation-stg'
inputs:
azureSubscription: 'omds-service-connection-stg'
action: 'Swap Slots'
WebAppName: 'app-odms-dictation-stg'
ResourceGroupName: 'stg-application-rg'
SourceSlot: 'staging'
SwapWithProduction: true
- job: migration
dependsOn: swap_slot
condition: succeeded('swap_slot')
displayName: DB migration
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: 'omds-service-connection-stg'
KeyVaultName: kv-odms-secret-stg
- task: CmdLine@2
displayName: migration
inputs:
script: >2
# DB接続情報書き換え
sed -i -e "s/DB_NAME_CCB/$(db-name-ccb)/g" ./dictation_server/db/dbconfig.yml
sed -i -e "s/DB_PASS/$(admin-db-pass)/g" ./dictation_server/db/dbconfig.yml
sed -i -e "s/DB_USERNAME/$(admin-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_ccb

View File

@ -1,363 +0,0 @@
# Pipeline側でKeyVaultやDocker、AppService等に対する操作権限を持ったServiceConnectionを作成しておくこと
# また、環境変数 STATIC_DICTATION_DEPLOYMENT_TOKEN の値として静的WebAppsのデプロイトークンを設定しておくこと
trigger:
branches:
include:
- release-ph1-enhance
tags:
include:
- stage-*
jobs:
- job: initialize
displayName: Initialize
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
persistCredentials: true
- script: |
git fetch origin release-ph1-enhance:release-ph1-enhance
if git merge-base --is-ancestor $(Build.SourceVersion) release-ph1-enhance; then
echo "This commit is in the release-ph1-enhance branch."
else
echo "This commit is not in the release-ph1-enhance branch."
exit 1
fi
displayName: 'タグが付けられたCommitがrelease-ph1-enhanceブランチに存在するか確認'
- job: backend_test
dependsOn: initialize
condition: succeeded('initialize')
displayName: UnitTest
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: Bash@3
displayName: Bash Script (Test)
inputs:
targetType: inline
workingDirectory: dictation_server/.devcontainer
script: |
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version
docker-compose -f pipeline-docker-compose.yml build
docker-compose -f pipeline-docker-compose.yml up -d
docker-compose exec -T dictation_server sudo npm ci
docker-compose exec -T dictation_server sudo npm run migrate:up:test
docker-compose exec -T dictation_server sudo npm run test
- job: backend_build
dependsOn: backend_test
condition: succeeded('backend_test')
displayName: Build And Push Backend Image
pool:
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: Docker@0
displayName: build
inputs:
azureSubscriptionEndpoint: 'omds-service-connection-stg'
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
dockerFile: DockerfileServerDictation.dockerfile
imageName: odmscloud/staging/dictation:$(Build.SourceVersion)
buildArguments: |
BUILD_VERSION=$(Build.SourceVersion)
- task: Docker@0
displayName: push
inputs:
azureSubscriptionEndpoint: 'omds-service-connection-stg'
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: odmscloud/staging/dictation:$(Build.SourceVersion)
- job: frontend_build_staging
dependsOn: backend_build
condition: succeeded('backend_build')
displayName: Build Frontend Files(staging)
variables:
storageAccountName: saomdspipeline
environment: staging
pool:
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:stg
- 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: 'omds-service-connection-stg'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az storage blob upload \
--auth-mode login \
--account-name $(storageAccountName) \
--container-name $(environment) \
--name $(Build.SourceVersion).zip \
--type block \
--overwrite \
--file $(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip
- job: frontend_build_production
dependsOn: frontend_build_staging
condition: succeeded('frontend_build_staging')
displayName: Build Frontend Files(production)
variables:
storageAccountName: saomdspipeline
environment: production
pool:
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:prod
- 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: 'omds-service-connection-stg'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az storage blob upload \
--auth-mode login \
--account-name $(storageAccountName) \
--container-name $(environment) \
--name $(Build.SourceVersion).zip \
--type block \
--overwrite \
--file $(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip
- job: function_test
dependsOn: frontend_build_production
condition: succeeded('frontend_build_production')
displayName: UnitTest
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: Bash@3
displayName: Bash Script (Test)
inputs:
targetType: inline
workingDirectory: dictation_function/.devcontainer
script: |
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version
docker-compose -f pipeline-docker-compose.yml build
docker-compose -f pipeline-docker-compose.yml up -d
docker-compose exec -T dictation_function sudo npm ci
docker-compose exec -T dictation_function sudo npm run test
- job: function_build
dependsOn: function_test
condition: succeeded('function_test')
displayName: Build And Push Function Image
pool:
name: odms-deploy-pipeline
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: Npm@1
displayName: npm ci
inputs:
command: ci
workingDir: dictation_function
verbose: false
- task: Docker@0
displayName: build
inputs:
azureSubscriptionEndpoint: 'omds-service-connection-stg'
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
dockerFile: DockerfileFunctionDictation.dockerfile
imageName: odmscloud/staging/dictation_function:$(Build.SourceVersion)
buildArguments: |
BUILD_VERSION=$(Build.SourceVersion)
- task: Docker@0
displayName: push
inputs:
azureSubscriptionEndpoint: 'omds-service-connection-stg'
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: odmscloud/staging/dictation_function:$(Build.SourceVersion)
- job: backend_deploy
dependsOn: function_build
condition: succeeded('function_build')
displayName: Backend Deploy
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: AzureWebAppContainer@1
inputs:
azureSubscription: 'omds-service-connection-stg'
appName: 'app-odms-dictation-stg'
deployToSlotOrASE: true
resourceGroupName: 'stg-application-rg'
slotName: 'staging'
containers: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/dictation:$(Build.SourceVersion)'
- job: frontend_deploy
dependsOn: backend_deploy
condition: succeeded('backend_deploy')
displayName: Deploy Frontend Files
variables:
storageAccountName: saomdspipeline
environment: staging
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: AzureCLI@2
inputs:
azureSubscription: 'omds-service-connection-stg'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az storage blob download \
--auth-mode login \
--account-name $(storageAccountName) \
--container-name $(environment) \
--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: function_deploy
dependsOn: frontend_deploy
condition: succeeded('frontend_deploy')
displayName: Function Deploy
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: AzureFunctionAppContainer@1
inputs:
azureSubscription: 'omds-service-connection-stg'
appName: 'func-odms-dictation-stg'
imageName: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/dictation_function:$(Build.SourceVersion)'
- job: smoke_test
dependsOn: function_deploy
condition: succeeded('function_deploy')
displayName: 'smoke test'
pool:
name: odms-deploy-pipeline
steps:
- checkout: self
clean: true
fetchDepth: 1
# スモークテスト用にjobを確保
- job: swap_slot
dependsOn: smoke_test
condition: succeeded('smoke_test')
displayName: 'Swap Staging and Production'
pool:
name: odms-deploy-pipeline
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: AzureAppServiceManage@0
displayName: 'Azure App Service Manage: app-odms-dictation-stg'
inputs:
azureSubscription: 'omds-service-connection-stg'
action: 'Swap Slots'
WebAppName: 'app-odms-dictation-stg'
ResourceGroupName: 'stg-application-rg'
SourceSlot: 'staging'
SwapWithProduction: true
- job: migration
dependsOn: swap_slot
condition: succeeded('swap_slot')
displayName: DB migration
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: 'omds-service-connection-stg'
KeyVaultName: kv-odms-secret-stg
- task: CmdLine@2
displayName: migration
inputs:
script: >2
# DB接続情報書き換え
sed -i -e "s/DB_NAME/$(db-name-ph1-enhance)/g" ./dictation_server/db/dbconfig.yml
sed -i -e "s/DB_PASS/$(admin-db-pass)/g" ./dictation_server/db/dbconfig.yml
sed -i -e "s/DB_USERNAME/$(admin-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

View File

@ -43,14 +43,11 @@ jobs:
targetType: inline
workingDirectory: dictation_server/.devcontainer
script: |
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version
docker-compose -f pipeline-docker-compose.yml build
docker-compose -f pipeline-docker-compose.yml up -d
docker-compose exec -T dictation_server sudo npm ci
docker-compose exec -T dictation_server sudo npm run migrate:up:test
docker-compose exec -T dictation_server sudo npm run test
docker-compose -f pipeline-docker-compose.yml build
docker-compose -f pipeline-docker-compose.yml up -d
docker-compose exec -T dictation_server sudo npm ci
docker-compose exec -T dictation_server sudo npm run migrate:up:test
docker-compose exec -T dictation_server sudo npm run test
- job: backend_build
dependsOn: backend_test
condition: succeeded('backend_test')
@ -173,32 +170,9 @@ jobs:
--type block \
--overwrite \
--file $(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip
- job: function_test
- job: function_build
dependsOn: frontend_build_production
condition: succeeded('frontend_build_production')
displayName: UnitTest
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: Bash@3
displayName: Bash Script (Test)
inputs:
targetType: inline
workingDirectory: dictation_function/.devcontainer
script: |
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version
docker-compose -f pipeline-docker-compose.yml build
docker-compose -f pipeline-docker-compose.yml up -d
docker-compose exec -T dictation_function sudo npm ci
docker-compose exec -T dictation_function sudo npm run test
- job: function_build
dependsOn: function_test
condition: succeeded('function_test')
displayName: Build And Push Function Image
pool:
name: odms-deploy-pipeline
@ -212,6 +186,32 @@ jobs:
command: ci
workingDir: dictation_function
verbose: false
- task: AzureKeyVault@2
displayName: 'Azure Key Vault: kv-odms-secret-stg'
inputs:
ConnectedServiceName: 'omds-service-connection-stg'
KeyVaultName: kv-odms-secret-stg
SecretsFilter: '*'
- task: Bash@3
displayName: Bash Script (Test)
inputs:
targetType: inline
script: |
cd dictation_function
npm run test
env:
TENANT_NAME: xxxxxxxxxxxx
SIGNIN_FLOW_NAME: xxxxxxxxxxxx
ADB2C_TENANT_ID: $(adb2c-tenant-id)
ADB2C_CLIENT_ID: $(adb2c-client-id)
ADB2C_CLIENT_SECRET: $(adb2c-client-secret)
ADB2C_ORIGIN: xxxxxx
SENDGRID_API_KEY: $(sendgrid-api-key)
MAIL_FROM: xxxxxx
APP_DOMAIN: xxxxxxxxx
REDIS_HOST: xxxxxxxxxxxx
REDIS_PORT: 0
REDIS_PASSWORD: xxxxxxxxxxxx
- task: Docker@0
displayName: build
inputs:

View File

@ -26,7 +26,6 @@
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"cookie-parser": "^1.4.6",
"csv": "^6.3.6",
"multer": "^1.4.5-lts.1",
"mysql2": "^2.3.3",
"reflect-metadata": "^0.1.13",
@ -4050,35 +4049,6 @@
"node": ">= 8"
}
},
"node_modules/csv": {
"version": "6.3.6",
"resolved": "https://registry.npmjs.org/csv/-/csv-6.3.6.tgz",
"integrity": "sha512-jsEsX2HhGp7xiwrJu5srQavKsh+HUJcCi78Ar3m4jlmFKRoTkkMy7ZZPP+LnQChmaztW+uj44oyfMb59daAs/Q==",
"dependencies": {
"csv-generate": "^4.3.1",
"csv-parse": "^5.5.3",
"csv-stringify": "^6.4.5",
"stream-transform": "^3.3.0"
},
"engines": {
"node": ">= 0.1.90"
}
},
"node_modules/csv-generate": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/csv-generate/-/csv-generate-4.3.1.tgz",
"integrity": "sha512-7YeeJq+44/I/O5N2sr2qBMcHZXhpfe38eh7DOFxyMtYO+Pir7kIfgFkW5MPksqKqqR6+/wX7UGoZm1Ot11151w=="
},
"node_modules/csv-parse": {
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.5.3.tgz",
"integrity": "sha512-v0KW6C0qlZzoGjk6u5tLmVfyZxNgPGXZsWTXshpAgKVGmGXzaVWGdlCFxNx5iuzcXT/oJN1HHM9DZKwtAtYa+A=="
},
"node_modules/csv-stringify": {
"version": "6.4.5",
"resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.4.5.tgz",
"integrity": "sha512-SPu1Vnh8U5EnzpNOi1NDBL5jU5Rx7DVHr15DNg9LXDTAbQlAVAmEbVt16wZvEW9Fu9Qt4Ji8kmeCJ2B1+4rFTQ=="
},
"node_modules/date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
@ -8684,11 +8654,6 @@
"npm": ">=6"
}
},
"node_modules/stream-transform": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-3.3.0.tgz",
"integrity": "sha512-pG1NeDdmErNYKtvTpFayrEueAmL0xVU5wd22V7InGnatl4Ocq3HY7dcXIKj629kXvYQvglCC7CeDIGAlx1RNGA=="
},
"node_modules/streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
@ -13062,32 +13027,6 @@
"which": "^2.0.1"
}
},
"csv": {
"version": "6.3.6",
"resolved": "https://registry.npmjs.org/csv/-/csv-6.3.6.tgz",
"integrity": "sha512-jsEsX2HhGp7xiwrJu5srQavKsh+HUJcCi78Ar3m4jlmFKRoTkkMy7ZZPP+LnQChmaztW+uj44oyfMb59daAs/Q==",
"requires": {
"csv-generate": "^4.3.1",
"csv-parse": "^5.5.3",
"csv-stringify": "^6.4.5",
"stream-transform": "^3.3.0"
}
},
"csv-generate": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/csv-generate/-/csv-generate-4.3.1.tgz",
"integrity": "sha512-7YeeJq+44/I/O5N2sr2qBMcHZXhpfe38eh7DOFxyMtYO+Pir7kIfgFkW5MPksqKqqR6+/wX7UGoZm1Ot11151w=="
},
"csv-parse": {
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.5.3.tgz",
"integrity": "sha512-v0KW6C0qlZzoGjk6u5tLmVfyZxNgPGXZsWTXshpAgKVGmGXzaVWGdlCFxNx5iuzcXT/oJN1HHM9DZKwtAtYa+A=="
},
"csv-stringify": {
"version": "6.4.5",
"resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.4.5.tgz",
"integrity": "sha512-SPu1Vnh8U5EnzpNOi1NDBL5jU5Rx7DVHr15DNg9LXDTAbQlAVAmEbVt16wZvEW9Fu9Qt4Ji8kmeCJ2B1+4rFTQ=="
},
"date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
@ -16618,11 +16557,6 @@
"resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz",
"integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw=="
},
"stream-transform": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-3.3.0.tgz",
"integrity": "sha512-pG1NeDdmErNYKtvTpFayrEueAmL0xVU5wd22V7InGnatl4Ocq3HY7dcXIKj629kXvYQvglCC7CeDIGAlx1RNGA=="
},
"streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",

View File

@ -45,8 +45,7 @@
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.0",
"swagger-cli": "^4.0.4",
"typeorm": "^0.3.20",
"csv": "^6.3.6"
"typeorm": "^0.3.20"
},
"devDependencies": {
"@types/express": "^4.17.17",

View File

@ -27,10 +27,6 @@ import { DeleteService } from "./features/delete/delete.service";
import { TransferModule } from "./features/transfer/transfer.module";
import { TransferController } from "./features/transfer/transfer.controller";
import { TransferService } from "./features/transfer/transfer.service";
import { VerificationController } from "./features/verification/verification.controller";
import { VerificationService } from "./features/verification/verification.service";
import { VerificationModule } from "./features/verification/verification.module";
@Module({
imports: [
ServeStaticModule.forRoot({
@ -45,7 +41,6 @@ import { VerificationModule } from "./features/verification/verification.module"
UsersModule,
TransferModule,
RegisterModule,
VerificationModule,
AccountsRepositoryModule,
UsersRepositoryModule,
SortCriteriaRepositoryModule,
@ -75,7 +70,6 @@ import { VerificationModule } from "./features/verification/verification.module"
UsersController,
DeleteController,
TransferController,
VerificationController,
],
providers: [
RegisterService,
@ -83,7 +77,6 @@ import { VerificationModule } from "./features/verification/verification.module"
UsersService,
DeleteService,
TransferService,
VerificationService,
],
})
export class AppModule {

View File

@ -18,8 +18,7 @@ export const makePassword = (): string => {
let autoGeneratedPassword: string = "";
while (!valid) {
autoGeneratedPassword = "";
// パスワードをランダムに決定+
// パスワードをランダムに決定
while (autoGeneratedPassword.length < passLength) {
// 上で決定したcharsの中からランダムに1文字ずつ追加
const index = Math.floor(Math.random() * chars.length);
@ -31,11 +30,6 @@ export const makePassword = (): string => {
valid =
autoGeneratedPassword.length == passLength &&
charaTypePattern.test(autoGeneratedPassword);
if (!valid) {
// autoGeneratedPasswordをログに出す
console.log("Password is not valid");
console.log(autoGeneratedPassword);
}
}
return autoGeneratedPassword;
};

View File

@ -34,11 +34,6 @@ export class csvInputFile {
wt19: string;
wt20: string;
}
export class csvInputFileWithRow extends csvInputFile {
row: number;
}
export class AccountsFileType {
accountId: number;
type: string;
@ -98,22 +93,6 @@ export class CardLicensesFile {
updated_by?: string;
}
export class AccountsMappingFile {
accountIdText: string;
accountIdNumber: number;
}
export class VerificationResultDetails {
input: string;
inputRow: number;
diffTargetTable: string;
columnName: string;
fileData: string;
databaseData: string;
reason: string;
}
export function isAccountsFileArray(obj: any): obj is AccountsFile[] {
return Array.isArray(obj) && obj.every((item) => isAccountsFile(item));
}
@ -223,65 +202,3 @@ export function isCardLicensesFile(obj: any): obj is CardLicensesFile {
(obj.updated_by === null || typeof obj.updated_by === "string")
);
}
export function isAccountsMappingFileArray(
obj: any
): obj is AccountsMappingFile[] {
return Array.isArray(obj) && obj.every((item) => isAccountsMappingFile(item));
}
export function isAccountsMappingFile(obj: any): obj is AccountsMappingFile {
return (
typeof obj === "object" &&
obj !== null &&
"accountIdText" in obj &&
"accountIdNumber" in obj &&
typeof obj.accountIdText === "string" &&
typeof obj.accountIdNumber === "number"
);
}
export function isCsvInputFileForValidateArray(obj: any): obj is csvInputFile[] {
return (
Array.isArray(obj) && obj.every((item) => isCsvInputFileForValidate(item))
);
}
export function isCsvInputFileForValidate(obj: any): obj is csvInputFile {
return (
typeof obj === "object" &&
"type" in obj &&
"account_id" in obj &&
"parent_id" in obj &&
"email" in obj &&
"company_name" in obj &&
"first_name" in obj &&
"last_name" in obj &&
"country" in obj &&
"state" in obj &&
"start_date" in obj &&
"expired_date" in obj &&
"user_email" in obj &&
"author_id" in obj &&
"recording_mode" in obj &&
"wt1" in obj &&
"wt2" in obj &&
"wt3" in obj &&
"wt4" in obj &&
"wt5" in obj &&
"wt6" in obj &&
"wt7" in obj &&
"wt8" in obj &&
"wt9" in obj &&
"wt10" in obj &&
"wt11" in obj &&
"wt12" in obj &&
"wt13" in obj &&
"wt14" in obj &&
"wt15" in obj &&
"wt16" in obj &&
"wt17" in obj &&
"wt18" in obj &&
"wt19" in obj &&
"wt20" in obj
);
}

View File

@ -79,7 +79,6 @@ export class AccountsService {
HttpStatus.INTERNAL_SERVER_ERROR
);
}
this.logger.log("idpにユーザーを作成成功");
// メールアドレス重複エラー
if (isConflictError(externalUser)) {
@ -91,7 +90,6 @@ export class AccountsService {
HttpStatus.BAD_REQUEST
);
}
this.logger.log("メールアドレスは重複していません");
let account: Account;
let user: User;
@ -140,7 +138,6 @@ export class AccountsService {
account.id,
country
);
this.logger.log("コンテナー作成成功");
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(

View File

@ -106,10 +106,8 @@ export class RegisterController {
}
for (const AccountsFile of accountsObject) {
this.logger.log("ランダムパスワード生成開始");
// ランダムなパスワードを生成する
const ramdomPassword = makePassword();
this.logger.log("ランダムパスワード生成完了");
// roleの設定
// roleの値がnullなら"none"、null以外ならroleの値、
// また、roleの値が"author"なら"author"を設定
@ -125,7 +123,6 @@ export class RegisterController {
// ありえないが、roleの値が"none"または"author"の文字列以外の場合はエラーを返す
throw new Error("Invalid role value");
}
this.logger.log("account生成開始");
await this.accountsService.createAccount(
context,
AccountsFile.companyName,

View File

@ -13,7 +13,7 @@ import { Request } from "express";
import { transferRequest, transferResponse } from "./types/types";
import { TransferService } from "./transfer.service";
import { makeContext } from "../../common/log";
import { csvInputFile, AccountsMappingFile } from "../../common/types/types";
import { csvInputFile } from "../../common/types/types";
import { makeErrorResponse } from "src/common/errors/makeErrorResponse";
import { AUTO_INCREMENT_START } from "../../constants";
@ApiTags("transfer")
@ -73,8 +73,7 @@ export class TransferController {
const matchList = line.match(regExp);
if (matchList) {
matchList.forEach((match) => {
// カンマを\に変換
const replaced = match.replace(/,/g, "\\");
const replaced = match.replace(/,/g, " ");
line = line.replace(match, replaced);
});
}
@ -96,50 +95,49 @@ export class TransferController {
HttpStatus.BAD_REQUEST
);
}
// data[1]がundefinedの場合、配列には格納しない
if (data[1] !== undefined) {
// バックスラッシュをカンマに戻す
data.forEach((value, index) => {
data[index] = value.replace(/\\/g, ",");
});
csvInputFile.push({
type: data[0],
account_id: data[1],
parent_id: data[2],
email: data[3],
company_name: data[4],
first_name: data[5],
last_name: data[6],
country: data[7],
state: data[8],
start_date: data[9],
expired_date: data[10],
user_email: data[11],
author_id: data[12],
recording_mode: data[13],
wt1: data[14],
wt2: data[15],
wt3: data[16],
wt4: data[17],
wt5: data[18],
wt6: data[19],
wt7: data[20],
wt8: data[21],
wt9: data[22],
wt10: data[23],
wt11: data[24],
wt12: data[25],
wt13: data[26],
wt14: data[27],
wt15: data[28],
wt16: data[29],
wt17: data[30],
wt18: data[31],
wt19: data[32],
wt20: data[33],
});
}
csvInputFile.push({
type: data[0],
account_id: data[1],
parent_id: data[2],
email: data[3],
company_name: data[4],
first_name: data[5],
last_name: data[6],
country: data[7],
state: data[8],
start_date: data[9],
expired_date: data[10],
user_email: data[11],
author_id: data[12],
recording_mode: data[13],
wt1: data[14],
wt2: data[15],
wt3: data[16],
wt4: data[17],
wt5: data[18],
wt6: data[19],
wt7: data[20],
wt8: data[21],
wt9: data[22],
wt10: data[23],
wt11: data[24],
wt12: data[25],
wt13: data[26],
wt14: data[27],
wt15: data[28],
wt16: data[29],
wt17: data[30],
wt18: data[31],
wt19: data[32],
wt20: data[33],
});
});
// 最後の行がundefinedの場合はその行を削除
if (csvInputFile[csvInputFile.length - 1].account_id === undefined) {
csvInputFile.pop();
}
// 各データのバリデーションチェック
await this.transferService.validateInputData(context, csvInputFile);
@ -151,21 +149,6 @@ export class TransferController {
accountIdListArray.forEach((accountId, index) => {
accountIdMap.set(accountId, index + AUTO_INCREMENT_START);
});
// アカウントID numberとstring対応表の出力
const accountsMappingFiles: AccountsMappingFile[] = [];
accountIdMap.forEach((value, key) => {
const accountsMappingFile = new AccountsMappingFile();
accountsMappingFile.accountIdNumber = value;
accountsMappingFile.accountIdText = key;
accountsMappingFiles.push(accountsMappingFile);
});
fs.writeFileSync(
`${inputFilePath}account_map.json`,
JSON.stringify(accountsMappingFiles)
);
// CSVファイルの変換
const transferResponseCsv = await this.transferService.transferInputData(
context,
@ -190,14 +173,6 @@ export class TransferController {
LicensesFile
);
// AuthorIDが重複している場合通番を付与する
const transferDuplicateAuthorResultUsers =
await this.transferService.transferDuplicateAuthor(
context,
resultDuplicateEmail.accountsFileLines,
resultDuplicateEmail.usersFileLines
);
// transferResponseCsvをつのJSONファイルの出力する(出力先はinputと同じにする)
const outputFilePath = body.inputFilePath;
const WorktypesFile = transferResponseCsv.worktypesFileLines;
@ -205,7 +180,7 @@ export class TransferController {
context,
outputFilePath,
resultDuplicateEmail.accountsFileLines,
transferDuplicateAuthorResultUsers,
resultDuplicateEmail.usersFileLines,
resultDuplicateEmail.licensesFileLines,
WorktypesFile
);

View File

@ -50,16 +50,10 @@ export class TransferService {
let usersFileLines: UsersFile[] = [];
let licensesFileLines: LicensesFile[] = [];
let worktypesFileLines: WorktypesFile[] = [];
let errorArray: string[] = [];
let userIdIndex = 0;
// authorIdとuserIdの対応関係を保持するMapを定義
const authorIdToUserIdMap: Map<string, number> = new Map();
// countryのリストを生成
const countryAccounts = csvInputFile.filter(
(item) => item.type === "Country"
);
// csvInputFileを一行読み込みする
csvInputFile.forEach((line) => {
// typeが"USER"以外の場合、アカウントデータの作成を行う
@ -69,13 +63,8 @@ export class TransferService {
(country) => country.label === line.country
)?.value;
// adminNameの変換(last_name + " "+ first_name)
// もしline.last_nameとline.first_nameが存在しない場合、line.admin_mailをnameにする
let adminName = line.email;
if (line.last_name && line.first_name) {
adminName = `${line.last_name} ${line.first_name}`;
// スペースが前後に入っている場合があるのでTrimする
adminName = adminName.trim();
}
const adminName = `${line.last_name} ${line.first_name}`;
// ランダムパスワードの生成(データ登録ツール側で行うのでやらない)
// common/password/password.tsのmakePasswordを使用
// const autoGeneratedPassword = makePassword();
@ -86,11 +75,9 @@ export class TransferService {
if (line.parent_id) {
parentAccountId = accountIdMap.get(line.parent_id);
}
// 万が一parent_idが入力されているのに存在しなかった場合は、エラー配列に追加する
// 万が一parent_idが入力されているのに存在しなかった場合は、nullを設定する。
if (parentAccountId === undefined) {
errorArray.push(
`parent_id is invalid. parent_id=${line.parent_id}`
);
parentAccountId = null;
}
// userIdIndexをインクリメントする
@ -111,13 +98,8 @@ export class TransferService {
authorId: null,
});
} else {
// typeが"USER"の場合、かつcountryのアカウントIDに所属していない場合
if (
line.type == MIGRATION_TYPE.USER &&
!countryAccounts.some(
(countryAccount) => countryAccount.account_id === line.account_id
)
) {
// typeが"USER"の場合
if (line.type == MIGRATION_TYPE.USER) {
// line.author_idが存在する場合のみユーザーデータを作成する
if (line.author_id) {
// userIdIndexをインクリメントする
@ -144,8 +126,8 @@ export class TransferService {
}
// ライセンスのデータの作成を行う
// line.expired_dateが"9999/12/31"で始まるデータの場合はデモライセンスなので登録しない
if (!line.expired_date.startsWith("9999/12/31")) {
// line.expired_dateが9999/12/31 23:59:59.997のデータの場合はデモライセンスなので登録しない
if (line.expired_date !== "9999/12/31 23:59:59.997") {
// authorIdが設定されてる場合、statusは"allocated"、allocated_user_idは対象のユーザID
// されていない場合、statusは"reusable"、allocated_user_idはnull
let status: string;
@ -194,15 +176,6 @@ export class TransferService {
}
}
});
// エラー配列に値が存在する場合はエラーファイルを出力する
if (errorArray.length > 0) {
const errorFileJson = JSON.stringify(errorArray);
fs.writeFileSync(`error.json`, errorFileJson);
throw new HttpException(
`errorArray is invalid. errorArray=${errorArray}`,
HttpStatus.BAD_REQUEST
);
}
return {
accountsFileTypeLines,
usersFileLines,
@ -238,40 +211,50 @@ export class TransferService {
try {
const relocatedAccounts: AccountsFile[] = [];
const dealerRecords: Map<number, number> = new Map();
const countryRecords: Map<number, number> = new Map();
const countryAccounts = accountsFileType.filter(
(item) => item.type === MIGRATION_TYPE.COUNTRY
);
const notCountryAccounts = accountsFileType.filter(
(item) => item.type !== MIGRATION_TYPE.COUNTRY
);
notCountryAccounts.forEach((notCountryAccount) => {
let assignDealerAccountId = notCountryAccount.dealerAccountId;
// 親アカウントIDがcountryの場合、countryの親アカウントIDを設定する
for (const countryAccount of countryAccounts) {
if (countryAccount.accountId === notCountryAccount.dealerAccountId) {
assignDealerAccountId = countryAccount.dealerAccountId;
// accountsFileTypeをループ
accountsFileType.forEach((account) => {
// Countryの場合はDistributorのアカウントIDと新たな親アカウントIDBCの組み合わせをMapに登録
if (account.type === MIGRATION_TYPE.COUNTRY) {
// 配下のDistributorアカウントを取得
const distributor = accountsFileType.find(
(distributor) =>
distributor.dealerAccountId === account.accountId &&
distributor.type === MIGRATION_TYPE.DISTRIBUTOR
);
if (distributor) {
countryRecords.set(distributor.accountId, account.dealerAccountId);
}
} else {
// Country以外のアカウントの場合は、そのまま登録
countryRecords.set(account.accountId, account.dealerAccountId);
}
});
const assignType = this.getAccountType(notCountryAccount.type);
// AccountsFileTypeのループを行い、階層情報の置換と新たな配列へのpushを行う
accountsFileType.forEach((account) => {
// Countryのレコードは除外する
if (account.type !== MIGRATION_TYPE.COUNTRY) {
const dealerAccountId =
countryRecords.get(account.dealerAccountId) ??
account.dealerAccountId;
const type = this.getAccountType(account.type);
const newAccount: AccountsFile = {
accountId: account.accountId,
type: type,
companyName: account.companyName,
country: account.country,
dealerAccountId: dealerAccountId,
adminName: account.adminName,
adminMail: account.adminMail,
userId: account.userId,
role: account.role,
authorId: account.authorId,
};
const newAccount: AccountsFile = {
accountId: notCountryAccount.accountId,
type: assignType,
companyName: notCountryAccount.companyName,
country: notCountryAccount.country,
dealerAccountId: assignDealerAccountId,
adminName: notCountryAccount.adminName,
adminMail: notCountryAccount.adminMail,
userId: notCountryAccount.userId,
role: notCountryAccount.role,
authorId: notCountryAccount.authorId,
};
relocatedAccounts.push(newAccount);
relocatedAccounts.push(newAccount);
}
});
return relocatedAccounts;
@ -367,8 +350,6 @@ export class TransferService {
);
try {
// エラー配列を定義
let errorArray: string[] = [];
// アカウントに対するworktypeのMap配列を作成する
const accountWorktypeMap = new Map<string, string[]>();
// csvInputFileのバリデーションチェックを行う
@ -388,13 +369,6 @@ export class TransferService {
HttpStatus.BAD_REQUEST
);
}
// typeがUSER以外の場合で、countryがnullの場合エラー配列に格納する
if (line.type !== MIGRATION_TYPE.USER) {
if (!line.country) {
// countryがnullの場合エラー配列に格納する
errorArray.push(`country is null. index=${index}`);
}
}
// countryのバリデーションチェック
if (line.country) {
if (!COUNTRY_LIST.find((country) => country.label === line.country)) {
@ -463,15 +437,6 @@ export class TransferService {
}
}
});
// エラー配列に値が存在する場合はエラーファイルを出力する
if (errorArray.length > 0) {
const errorFileJson = JSON.stringify(errorArray);
fs.writeFileSync(`error.json`, errorFileJson);
throw new HttpException(
`errorArray is invalid. errorArray=${errorArray}`,
HttpStatus.BAD_REQUEST
);
}
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
@ -511,7 +476,7 @@ export class TransferService {
// accountsFileLinesの行ループ
accountsFileLines.forEach((account) => {
const duplicateAdminMail = newAccountsFileLines.find(
(a) => a.adminMail.toLowerCase() === account.adminMail.toLowerCase() // メールアドレスは大文字小文字を区別しない
(a) => a.adminMail === account.adminMail
);
if (duplicateAdminMail) {
@ -531,7 +496,7 @@ export class TransferService {
// usersFileLinesの行ループ
usersFileLines.forEach((user) => {
const duplicateUserEmail = newUsersFileLines.find(
(u) => u.email.toLowerCase() === user.email.toLowerCase() // メールアドレスは大文字小文字を区別しない
(u) => u.email === user.email
);
if (duplicateUserEmail) {
@ -553,7 +518,7 @@ export class TransferService {
}
// newAccountsFileLinesとの突合せ
const duplicateAdminUserEmail = newAccountsFileLines.find(
(a) => a.adminMail.toLowerCase() === user.email.toLowerCase() // メールアドレスは大文字小文字を区別しない
(a) => a.adminMail === user.email
);
// 重複がある場合
if (duplicateAdminUserEmail) {
@ -624,74 +589,4 @@ export class TransferService {
);
}
}
/**
* transferDuplicateAuthor
* @param accountsFileLines: AccountsFile[]
* @param usersFileLines: UsersFile[]
* @returns UsersFile[]
*/
async transferDuplicateAuthor(
context: Context,
accountsFileLines: AccountsFile[],
usersFileLines: UsersFile[]
): Promise<UsersFile[]> {
// パラメータ内容が長大なのでログには出さない
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.transferDuplicateAuthor.name}`
);
try {
const newUsersFileLines: UsersFile[] = [];
for (const accountsFileLine of accountsFileLines) {
let duplicateSequence: number = 2;
let authorIdList: String[] = [];
// メールアドレス重複時はアカウントにもAuthorIdが設定されるので重複チェック用のリストに追加しておく
if (accountsFileLine.authorId) {
authorIdList.push(accountsFileLine.authorId);
}
const targetaccountUsers = usersFileLines.filter(
(item) => item.accountId === accountsFileLine.accountId
);
for (const targetaccountUser of targetaccountUsers) {
let assignAuthorId = targetaccountUser.authorId;
if (authorIdList.includes(targetaccountUser.authorId)) {
// 同じauthorIdがいる場合、自分のauthorIdに連番を付与する
assignAuthorId = assignAuthorId + duplicateSequence;
duplicateSequence = duplicateSequence + 1;
}
authorIdList.push(targetaccountUser.authorId);
// 新しいAuthorIdのユーザに詰め替え
const newUser: UsersFile = {
accountId: targetaccountUser.accountId,
userId: targetaccountUser.userId,
name: targetaccountUser.name,
role: targetaccountUser.role,
authorId: assignAuthorId,
email: targetaccountUser.email,
};
newUsersFileLines.push(newUser);
}
}
return newUsersFileLines;
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${
this.transferDuplicateAuthor.name
}`
);
}
}
}

View File

@ -74,9 +74,6 @@ export class UsersService {
accountId,
authorId
);
this.logger.log(
`[${context.getTrackingId()}] isAuthorIdDuplicated=${isAuthorIdDuplicated}`
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
@ -91,10 +88,9 @@ export class UsersService {
);
}
}
this.logger.log("ランダムパスワード生成開始");
// ランダムなパスワードを生成する
const ramdomPassword = makePassword();
this.logger.log("ランダムパスワード生成完了");
//Azure AD B2Cにユーザーを新規登録する
let externalUser: { sub: string } | ConflictError;

View File

@ -1,9 +0,0 @@
import { ApiProperty } from '@nestjs/swagger';
export class VerificationRequest {
@ApiProperty()
inputFilePath: string;
}
export class VerificationResponse {}

View File

@ -1,148 +0,0 @@
import {
Body,
Controller,
HttpStatus,
Post,
Req,
Logger,
HttpException,
} from "@nestjs/common";
import { makeErrorResponse } from "../../common/error/makeErrorResponse";
import fs from "fs";
import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
import { Request } from "express";
import { VerificationRequest, VerificationResponse } from "./types/types";
import { VerificationService } from "./verification.service";
import { makeContext } from "../../common/log";
import {
csvInputFileWithRow,
isAccountsMappingFileArray,
isCardLicensesFileArray,
isCsvInputFileForValidateArray,
} from "../../common/types/types";
import * as csv from "csv";
@ApiTags("verification")
@Controller("verification")
export class VerificationController {
private readonly logger = new Logger(VerificationController.name);
constructor(private readonly verificationService: VerificationService) {}
@Post()
@ApiResponse({
status: HttpStatus.OK,
type: VerificationResponse,
description: "成功時のレスポンス",
})
@ApiResponse({
status: HttpStatus.INTERNAL_SERVER_ERROR,
description: "想定外のサーバーエラー",
})
@ApiOperation({ operationId: "dataVerification" })
async dataVerification(
@Body() body: VerificationRequest,
@Req() req: Request
): Promise<VerificationResponse> {
const context = makeContext("iko", "varification");
const inputFilePath = body.inputFilePath;
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.dataVerification.name
} | params: { inputFilePath: ${inputFilePath}};`
);
try {
// 読み込みファイルのフルパス
const accountTransitionFileFullPath =
inputFilePath + "Account_transition.csv";
const accountMapFileFullPath = inputFilePath + "account_map.json";
const cardLicensesFileFullPath = inputFilePath + "cardLicenses.json";
// ファイル存在チェックと読み込み
if (!fs.existsSync(accountTransitionFileFullPath)) {
this.logger.error(
`file not exists from ${accountTransitionFileFullPath}`
);
throw new Error(
`file not exists from ${accountTransitionFileFullPath}`
);
}
if (!fs.existsSync(accountMapFileFullPath)) {
this.logger.error(`file not exists from ${accountMapFileFullPath}`);
throw new Error(`file not exists from ${accountMapFileFullPath}`);
}
if (!fs.existsSync(cardLicensesFileFullPath)) {
this.logger.error(`file not exists from ${cardLicensesFileFullPath}`);
throw new Error(`file not exists from ${cardLicensesFileFullPath}`);
}
// カードライセンスの登録用ファイル読み込み
const cardLicensesObject = JSON.parse(
fs.readFileSync(cardLicensesFileFullPath, "utf8")
);
// 型ガードcardLicenses
if (!isCardLicensesFileArray(cardLicensesObject)) {
throw new Error("input file is not cardLicensesInputFiles");
}
// アカウントIDマッピング用ファイル読み込み
const accountsMapObject = JSON.parse(
fs.readFileSync(accountMapFileFullPath, "utf8")
);
// 型ガードaccountsMapingFile
if (!isAccountsMappingFileArray(accountsMapObject)) {
throw new Error("input file is not accountsMapingFile");
}
// 移行用csvファイルの読み込みcsv parse
fs.createReadStream(accountTransitionFileFullPath).pipe(
csv.parse({ columns: true, delimiter: "," }, (err, csvInputFiles) => {
// 型ガードcsvInputFile
if (!isCsvInputFileForValidateArray(csvInputFiles)) {
throw new Error("input file is not csvInputFile");
}
const csvInputFileswithRows: csvInputFileWithRow[] = [];
let rowCount = 2; // csvの何行目かを表す変数。ヘッダ行があるので2から開始
for (const csvInputFile of csvInputFiles) {
const csvInputFileswithRow: csvInputFileWithRow = {
...csvInputFile,
row: rowCount
};
csvInputFileswithRows.push(csvInputFileswithRow);
rowCount = rowCount + 1;
}
this.verificationService.varificationData(
context,
inputFilePath,
csvInputFileswithRows,
accountsMapObject,
cardLicensesObject
);
})
);
return {};
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.dataVerification.name}`
);
}
}
}
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}

View File

@ -1,17 +0,0 @@
import { Module } from "@nestjs/common";
import { VerificationController } from "./verification.controller";
import { VerificationService } from "./verification.service";
import { LicensesRepositoryModule } from "../../repositories/licenses/licenses.repository.module";
import { AccountsRepositoryModule } from "../../repositories/accounts/accounts.repository.module";
import { UsersRepositoryModule } from "../../repositories//users/users.repository.module";
@Module({
imports: [
LicensesRepositoryModule,
AccountsRepositoryModule,
UsersRepositoryModule,
],
controllers: [VerificationController],
providers: [VerificationService],
})
export class VerificationModule {}

View File

@ -1,747 +0,0 @@
import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common";
import { Context } from "../../common/log";
import {
AccountsMappingFile,
CardLicensesFile,
csvInputFileWithRow,
VerificationResultDetails,
} from "../../common/types/types";
import {
AUTO_INCREMENT_START,
MIGRATION_TYPE,
COUNTRY_LIST,
} from "../../constants/index";
import { makeErrorResponse } from "../../common/error/makeErrorResponse";
import { LicensesRepositoryService } from "../../repositories/licenses/licenses.repository.service";
import {
License,
CardLicense,
} from "../../repositories/licenses/entity/license.entity";
import { AccountsRepositoryService } from "../../repositories/accounts/accounts.repository.service";
import { UsersRepositoryService } from "../../repositories//users/users.repository.service";
import { Account } from "src/repositories/accounts/entity/account.entity";
import fs from "fs";
@Injectable()
export class VerificationService {
constructor(
private readonly AccountsRepository: AccountsRepositoryService,
private readonly UsersRepository: UsersRepositoryService,
private readonly licensesRepository: LicensesRepositoryService
) {}
private readonly logger = new Logger(VerificationService.name);
/**
* Verification Data
* @param inputFilePath: string
*/
async varificationData(
context: Context,
inputFilePath: string,
csvInputFiles: csvInputFileWithRow[],
accountsMappingInputFiles: AccountsMappingFile[],
cardlicensesInputFiles: CardLicensesFile[]
): Promise<void> {
// パラメータ内容が長大なのでログには出さない
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.varificationData.name}`
);
// this.logger.log(csvInputFiles);
try {
// 件数情報の取得
this.logger.log(`入力ファイルから件数情報を取得する`);
const accountFromFile = csvInputFiles.filter(
(item) => item.type !== "USER" && item.type !== "Country"
);
const accountCountFromFile = accountFromFile.length;
const cardLicensesCountFromFile = cardlicensesInputFiles.length;
const licensesCountFromFile =
csvInputFiles.filter(
(item) =>
item.type === "USER" && !item.expired_date.startsWith("9999/12/31")
).length + cardLicensesCountFromFile;
// 管理ユーザ数のカウント
const administratorCountFromFile = accountCountFromFile;
// 一般ユーザ数のカウント
// countryのアカウントに所属するユーザをカウント対象外とする
const countryAccountFromFile = csvInputFiles.filter(
(item) => item.type === "Country"
);
// USER、かつuser_emailが設定なし、かつcountryのアカウントID以外をユーザとする
const normaluserFromFile = csvInputFiles.filter(
(item) =>
item.type === "USER" &&
item.user_email.length !== 0 &&
!countryAccountFromFile.some(
(countryItem) => countryItem.account_id === item.account_id
)
);
const normaluserCountFromFile = normaluserFromFile.length;
// ユーザ重複数のカウント
let mailAdresses: string[] = [];
accountFromFile.forEach((item) => {
// メールアドレスの要素を配列に追加
if (item.email.length !== 0) {
mailAdresses.push(item.email);
}
});
normaluserFromFile.forEach((item) => {
// メールアドレスの要素を配列に追加
if (item.user_email.length !== 0) {
mailAdresses.push(item.user_email);
}
});
// 重複する要素を抽出
const duplicates: { [key: string]: number } = {};
mailAdresses.forEach((str) => {
duplicates[str.toLowerCase()] =
(duplicates[str.toLowerCase()] || 0) + 1;
});
// 重複する要素と件数を表示
let duplicateCount = 0;
Object.keys(duplicates).forEach((key) => {
const count = duplicates[key];
if (count > 1) {
// 重複件数をカウント
duplicateCount = duplicateCount + (count - 1);
//console.log(`${key}が${count}件`);
}
});
const userCountFromFile =
administratorCountFromFile + normaluserCountFromFile - duplicateCount;
this.logger.log(`accountCountFromFile=${accountCountFromFile}`);
this.logger.log(`cardLicensesCountFromFile=${cardLicensesCountFromFile}`);
this.logger.log(`licensesCountFromFile=${licensesCountFromFile}`);
this.logger.log(`userCountFromFile=${userCountFromFile}`);
// DBから情報を取得する
this.logger.log(`DBの情報を取得する`);
const accounts = await this.AccountsRepository.getAllAccounts(context);
const users = await this.UsersRepository.getAllUsers(context);
const licenses = await this.licensesRepository.getAllLicenses(context);
const cardLicenses = await this.licensesRepository.getAllCardLicense(
context
);
// DB件数のカウント
this.logger.log(`DBの情報から件数を取得する`);
const accountsCountFromDB = accounts.length;
const usersCountFromDB = users.length;
const licensesCountFromDB = licenses.length;
const cardLicensesCountFromDB = cardLicenses.length;
this.logger.log(`accountsCountFromDB=${accountsCountFromDB}`);
this.logger.log(`usersCountFromDB=${usersCountFromDB}`);
this.logger.log(`licensesCountFromDB=${licensesCountFromDB}`);
this.logger.log(`cardLicensesCountFromDB=${cardLicensesCountFromDB}`);
// エラー情報の定義
const VerificationResultDetails: VerificationResultDetails[] = [];
// カードライセンス関連の情報突き合わせ
this.logger.log(`カードライセンス関連の情報突き合わせ`);
const isCardDetailNoError = compareCardLicenses(
VerificationResultDetails,
cardlicensesInputFiles,
cardLicenses,
licenses
);
// ライセンス関連の情報突き合わせ
this.logger.log(`ライセンス関連の情報突き合わせ`);
const isLicensesDetailNoError = compareLicenses(
VerificationResultDetails,
csvInputFiles.filter(
(item) =>
item.type === "USER" && !item.expired_date.startsWith("9999/12/31")
),
licenses.filter((item) => item.expiry_date !== null),
accountsMappingInputFiles
);
// アカウント情報の突き合わせ
this.logger.log(`アカウント関連の情報突き合わせ`);
const isAccountsDetailNoError = compareAccounts(
VerificationResultDetails,
csvInputFiles.filter(
(item) => item.type !== "USER" && item.type !== "Country"
),
csvInputFiles.filter((item) => item.type === "Country"),
accounts,
accountsMappingInputFiles
);
// 結果の判定と出力
this.logger.log(`結果の判定と出力`);
const isAccountCountNoDifference =
accountCountFromFile === accountsCountFromDB;
const isUsersCountNoDifference = userCountFromFile === usersCountFromDB;
const isLicensesCountNoDifference =
licensesCountFromFile === licensesCountFromDB;
const isCardLicensesCountNoDifference =
cardLicensesCountFromFile === cardLicensesCountFromDB;
const isNoDetailError = VerificationResultDetails.length === 0;
const isSummaryNoError =
isAccountCountNoDifference &&
isUsersCountNoDifference &&
isLicensesCountNoDifference &&
isCardLicensesCountNoDifference &&
isNoDetailError;
const summaryString = `
${isSummaryNoError ? "OK" : "NG"}
${
isAccountCountNoDifference ? "OK" : "NG"
}csv件数:${accountCountFromFile}/DB件数:${accountsCountFromDB}
${
isLicensesCountNoDifference ? "OK" : "NG"
}csv件数:${licensesCountFromFile}/DB件数:${licensesCountFromDB}
${
isCardLicensesCountNoDifference ? "OK" : "NG"
}csv件数:${cardLicensesCountFromFile}/DB件数:${cardLicensesCountFromDB}
${
isUsersCountNoDifference ? "OK" : "NG"
}csv件数:${userCountFromFile}/DB件数:${usersCountFromDB}
${isAccountsDetailNoError ? "OK" : "NG"}
${isCardDetailNoError ? "OK" : "NG"}
${isLicensesDetailNoError ? "OK" : "NG"}
`;
// サマリファイルの書き込み
fs.writeFileSync(`${inputFilePath}resultsummary.txt`, summaryString);
// 詳細ファイルの書き込み
// 配列をJSON文字列に変換
const jsonContent = JSON.stringify(VerificationResultDetails, null, 2);
// JSONをファイルに書き込み
fs.writeFileSync(`${inputFilePath}resultdetail.json`, jsonContent);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.varificationData.name}`
);
}
}
}
// dateを任意のフォーマットに変換する
const getFormattedDate = (
date: Date | null,
format: string,
padHours: boolean = false // trueの場合、hhについてゼロパディングする00→0、01→1、23→23
) => {
if (!date) {
return null;
}
const symbol = {
M: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
m: date.getMinutes(),
s: date.getSeconds(),
};
// hhの値をゼロパディングするかどうかのフラグを確認
const hourSymbol = padHours ? "hh" : "h";
const formatted = format.replace(/(M+|d+|h+|m+|s+)/g, (v) =>
(
(v.length > 1 && v !== hourSymbol ? "0" : "") +
symbol[v.slice(-1) as keyof typeof symbol]
).slice(-2)
);
return formatted.replace(/(y+)/g, (v) =>
date.getFullYear().toString().slice(-v.length)
);
};
// 親の階層がcountryの場合、countryの親を返却する
function transrateCountryHierarchy(
countriesFromFile: csvInputFileWithRow[],
targetParentAccountIdString: string
): string {
for (const countryFromFile of countriesFromFile) {
if (countryFromFile.account_id === targetParentAccountIdString) {
return countryFromFile.parent_id;
}
}
return targetParentAccountIdString;
}
// アカウントIDnumberを対応するアカウントIDstringに変換する
function findAccountIdText(
accountsMappings: AccountsMappingFile[],
targetAccountIdNumber: number
): string {
if (targetAccountIdNumber == null) {
return "";
}
for (const accountsMapping of accountsMappings) {
if (accountsMapping.accountIdNumber === targetAccountIdNumber) {
return accountsMapping.accountIdText;
}
}
return `NO_MATCHED_ACCOUNTID_${targetAccountIdNumber}`; // マッチするものが見つからない場合
}
// 階層numberを対応する階層stringに変換する
function getMigrationTypeByNumber(numberValue: number): string {
switch (numberValue) {
case 1:
return MIGRATION_TYPE.ADMINISTRATOR;
case 2:
return MIGRATION_TYPE.BC;
case 3:
return MIGRATION_TYPE.DISTRIBUTOR;
case 4:
return MIGRATION_TYPE.DEALER;
case 5:
return MIGRATION_TYPE.CUSTOMER;
default:
return `NO_MATCHED_TIER_${numberValue}`;
}
}
// 国(省略版)を対応する国(非省略版)に変換する
function getCountryLabelByValue(value: string): string {
const country = COUNTRY_LIST.find((country) => country.value === value);
return country ? country.label : `NO_MATCHED_COUNTRY_${value}`;
}
// カードライセンス情報の突き合わせを行い、エラー時はエラー情報配列に情報追加する
function compareCardLicenses(
VerificationResultDetails: VerificationResultDetails[],
cardlicensesInputFiles: CardLicensesFile[],
cardLicenses: CardLicense[],
licenses: License[]
): boolean {
let isNoError = true;
let row = 1; // カードライセンスファイルの行数
for (const cardlicensesInputFile of cardlicensesInputFiles) {
const filterdCardLicenses = cardLicenses.filter(
(cardLicenses) =>
cardLicenses.card_license_key === cardlicensesInputFile.card_license_key
);
if (filterdCardLicenses.length === 0) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "cardLicenses",
columnName: "card_license_key",
fileData: cardlicensesInputFile.card_license_key,
databaseData: "-",
reason: "レコード無し",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
continue;
}
/* issue_id
if (cardlicensesInputFile.issue_id !== filterdCardLicenses[0].issue_id) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
diffTargetTable: "cardLicenses",
columnName: "issue_id",
fileData: cardlicensesInputFile.issue_id.toString(),
databaseData: filterdCardLicenses[0].issue_id.toString(),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
*/
const formattedFileActivated = getFormattedDate(
cardlicensesInputFile.activated_at
? new Date(cardlicensesInputFile.activated_at)
: null,
`yyyy/MM/dd hh:mm:ss`,
true
);
const formattedDbActivated = getFormattedDate(
filterdCardLicenses[0].activated_at,
`yyyy/MM/dd hh:mm:ss`,
true
);
if (formattedFileActivated !== formattedDbActivated) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "cardLicenses",
columnName: "activated_at",
fileData: formattedFileActivated,
databaseData: formattedDbActivated,
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
const filterdLicenses = licenses.filter(
(licenses) => licenses.id === filterdCardLicenses[0].license_id
);
if (filterdLicenses.length === 0) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "id",
fileData: filterdCardLicenses[0].license_id.toString(),
databaseData: "-",
reason: "紐つくライセンスのレコード無し",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
continue;
}
if (filterdLicenses[0].expiry_date !== null) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "expiry_date",
fileData: null,
databaseData: getFormattedDate(
filterdLicenses[0].expiry_date,
`yyyy/MM/dd hh:mm:ss`
),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
if (filterdLicenses[0].account_id !== AUTO_INCREMENT_START) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "account_id",
fileData: AUTO_INCREMENT_START.toString(),
databaseData: filterdLicenses[0].account_id.toString(),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
if (filterdLicenses[0].type !== "CARD") {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "type",
fileData: "CARD",
databaseData: filterdLicenses[0].type,
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
if (filterdLicenses[0].status !== "Unallocated") {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "status",
fileData: "Unallocated",
databaseData: filterdLicenses[0].status,
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
if (filterdLicenses[0].allocated_user_id !== null) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "allocated_user_id",
fileData: null,
databaseData: filterdLicenses[0].allocated_user_id.toString(),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
if (filterdLicenses[0].order_id !== null) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "order_id",
fileData: null,
databaseData: filterdLicenses[0].order_id.toString(),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
if (filterdLicenses[0].deleted_at !== null) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "deleted_at",
fileData: null,
databaseData: getFormattedDate(
filterdLicenses[0].deleted_at,
`yyyy/MM/dd hh:mm:ss`
),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
if (filterdLicenses[0].delete_order_id !== null) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "delete_order_id",
fileData: null,
databaseData: filterdLicenses[0].delete_order_id.toString(),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
row = row + 1;
}
return isNoError;
}
// ライセンス情報の突き合わせを行い、エラー時はエラー情報配列に情報追加する
function compareLicenses(
VerificationResultDetails: VerificationResultDetails[],
licensesFromFile: csvInputFileWithRow[],
licensesFromDatabase: License[],
accountsMappingInputFiles: AccountsMappingFile[]
): boolean {
let isNoError = true;
for (let i = 0; i < licensesFromFile.length; i++) {
if (
!licensesFromDatabase[i] ||
licensesFromFile[i].account_id !==
findAccountIdText(
accountsMappingInputFiles,
licensesFromDatabase[i].account_id
)
) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "Account_transition",
inputRow: licensesFromFile[i].row,
diffTargetTable: "licenses",
columnName: "account_id",
fileData: licensesFromFile[i].account_id,
databaseData: licensesFromDatabase[i]
? findAccountIdText(
accountsMappingInputFiles,
licensesFromDatabase[i].account_id
) + `(${licensesFromDatabase[i].account_id})`
: "undifined",
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
// expiry_dateについて、時はゼロパディングした値で比較する×0109 ○19
if (
!licensesFromDatabase[i] ||
getFormattedDate(
licensesFromFile[i].expired_date
? new Date(licensesFromFile[i].expired_date)
: null,
`yyyy/MM/dd hh:mm:ss`,
true
) !==
getFormattedDate(
licensesFromDatabase[i].expiry_date,
`yyyy/MM/dd hh:mm:ss`,
true
)
) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "Account_transition",
inputRow: licensesFromFile[i].row,
diffTargetTable: "licenses",
columnName: "expired_date",
fileData: getFormattedDate(
licensesFromFile[i].expired_date
? new Date(licensesFromFile[i].expired_date)
: null,
`yyyy/MM/dd hh:mm:ss`,
true
),
databaseData: licensesFromDatabase[i]
? getFormattedDate(
licensesFromDatabase[i].expiry_date,
`yyyy/MM/dd hh:mm:ss`,
true
)
: "undifined",
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
}
return isNoError;
}
// アカウント情報の突き合わせを行い、エラー時はエラー情報配列に情報追加する
function compareAccounts(
VerificationResultDetails: VerificationResultDetails[],
accountsFromFile: csvInputFileWithRow[],
countriesFromFile: csvInputFileWithRow[],
accountsFromDatabase: Account[],
accountsMappingInputFiles: AccountsMappingFile[]
): boolean {
let isNoError = true;
for (const accountFromFile of accountsFromFile) {
// DBレコードの存在チェック
const filterdAccounts = accountsFromDatabase.filter(
(accountsFromDatabase) =>
findAccountIdText(
accountsMappingInputFiles,
accountsFromDatabase.id
) === accountFromFile.account_id
);
if (filterdAccounts.length === 0) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "Account_transition",
inputRow: accountFromFile.row,
diffTargetTable: "accounts",
columnName: "account_id",
fileData: accountFromFile.account_id,
databaseData: "-",
reason: "レコード無し",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
continue;
}
// 項目チェックparent_account_id
const transratedParentId = transrateCountryHierarchy(
countriesFromFile,
accountFromFile.parent_id
);
if (
transratedParentId !==
findAccountIdText(
accountsMappingInputFiles,
filterdAccounts[0].parent_account_id
)
) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "Account_transition",
inputRow: accountFromFile.row,
diffTargetTable: "accounts",
columnName: "parent_account_id",
fileData:
transratedParentId === accountFromFile.parent_id
? accountFromFile.parent_id
: `${transratedParentId}(${accountFromFile.parent_id})`,
databaseData:
findAccountIdText(
accountsMappingInputFiles,
filterdAccounts[0].parent_account_id
) + `(${filterdAccounts[0].parent_account_id})`,
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
continue;
}
// 項目チェックtier
if (
accountFromFile.type !== getMigrationTypeByNumber(filterdAccounts[0].tier)
) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "Account_transition",
inputRow: accountFromFile.row,
diffTargetTable: "accounts",
columnName: "tier",
fileData: accountFromFile.type,
databaseData: getMigrationTypeByNumber(filterdAccounts[0].tier),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
continue;
}
// 項目チェックcountry
if (
accountFromFile.country !==
getCountryLabelByValue(filterdAccounts[0].country)
) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "Account_transition",
inputRow: accountFromFile.row,
diffTargetTable: "accounts",
columnName: "country",
fileData: accountFromFile.country,
databaseData: getCountryLabelByValue(filterdAccounts[0].country),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
continue;
}
// 項目チェックcompany_name
if (accountFromFile.company_name !== filterdAccounts[0].company_name) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "Account_transition",
inputRow: accountFromFile.row,
diffTargetTable: "accounts",
columnName: "company_name",
fileData: accountFromFile.company_name,
databaseData: filterdAccounts[0].company_name,
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
continue;
}
}
return isNoError;
}

View File

@ -64,57 +64,41 @@ export class AdB2cService {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.createUser.name}`
);
const retryCount: number = 3;
let retry = 0;
while (retry < retryCount) {
try {
// ユーザをADB2Cに登録
const newUser = await this.graphClient.api("users/").post({
accountEnabled: true,
displayName: username,
passwordPolicies: "DisableStrongPassword",
passwordProfile: {
forceChangePasswordNextSignIn: false,
password: password,
try {
// ユーザをADB2Cに登録
const newUser = await this.graphClient.api("users/").post({
accountEnabled: true,
displayName: username,
passwordPolicies: "DisableStrongPassword",
passwordProfile: {
forceChangePasswordNextSignIn: false,
password: password,
},
identities: [
{
signinType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: `${this.tenantName}.onmicrosoft.com`,
issuerAssignedId: email,
},
identities: [
{
signinType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: `${this.tenantName}.onmicrosoft.com`,
issuerAssignedId: email,
},
],
});
this.logger.log(
`[${context.getTrackingId()}] [ADB2C CREATE] newUser: ${newUser}`
);
return { sub: newUser.id };
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e?.statusCode === 400 && e?.body) {
const error = JSON.parse(e.body);
],
});
return { sub: newUser.id };
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e?.statusCode === 400 && e?.body) {
const error = JSON.parse(e.body);
// エラーが競合エラーである場合は、メールアドレス重複としてエラーを返す
if (error?.details?.find((x) => x.code === "ObjectConflict")) {
return { reason: "email", message: "ObjectConflict" };
}
// エラーが競合エラーである場合は、メールアドレス重複としてエラーを返す
if (error?.details?.find((x) => x.code === "ObjectConflict")) {
return { reason: "email", message: "ObjectConflict" };
}
if (++retry < retryCount) {
this.logger.log(`ADB2Cエラー発生。5秒sleepしてリトライします (${retry}/${retryCount})...`);
await new Promise(resolve => setTimeout(resolve, 5000));
} else {
this.logger.log(`リトライ数が上限に達したのでエラーを返却します`);
throw e;
}
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.createUser.name}`
);
}
throw e;
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.createUser.name}`
);
}
}

View File

@ -163,21 +163,4 @@ export class AccountsRepositoryService {
);
});
}
/**
*
* @returns Account[]
*/
async getAllAccounts(
context: Context,
): Promise<Account[]> {
return await this.dataSource.transaction(async (entityManager) => {
const accountsRepo = entityManager.getRepository(Account);
const accouts = accountsRepo.find({
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
});
return accouts;
});
}
}

View File

@ -166,34 +166,4 @@ export class LicensesRepositoryService {
return {};
});
}
/**
*
* @returns License[]
*/
async getAllLicenses(context: Context): Promise<License[]> {
return await this.dataSource.transaction(async (entityManager) => {
const licenseRepo = entityManager.getRepository(License);
const licenses = licenseRepo.find({
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
});
return licenses;
});
}
/**
*
* @returns CardLicense[]
*/
async getAllCardLicense(context: Context): Promise<CardLicense[]> {
return await this.dataSource.transaction(async (entityManager) => {
const cardLicenseRepo = entityManager.getRepository(CardLicense);
const cardLicenses = cardLicenseRepo.find({
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
});
return cardLicenses;
});
}
}

View File

@ -138,20 +138,4 @@ export class UsersRepositoryService {
await deleteEntity(usersRepo, { id: userId }, this.isCommentOut, context);
});
}
/**
*
* @returns User[]
*/
async getAllUsers(context: Context): Promise<User[]> {
return await this.dataSource.transaction(async (entityManager) => {
const userRepo = entityManager.getRepository(User);
const users = userRepo.find({
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
});
return users;
});
}
}

View File

@ -17,6 +17,10 @@ RUN bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${USERNAME}" "$
&& apt-get install default-jre -y \
&& apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts
# Update NPM
RUN npm install -g npm
# Install mob
RUN curl -sL install.mob.sh | sh

View File

@ -1,5 +1,5 @@
VITE_STAGE=production
VITE_B2C_CLIENTID=ea6e2535-c914-4889-8659-7ca1ec2e420d
VITE_B2C_AUTHORITY=https://adb2codmsproduction.b2clogin.com/adb2codmsproduction.onmicrosoft.com/b2c_1_signin_production
VITE_B2C_KNOWNAUTHORITIES=adb2codmsproduction.b2clogin.com
VITE_B2C_CLIENTID=b0ec473b-6b2b-4f12-adc6-39a24ebe6a3f
VITE_B2C_AUTHORITY=https://adb2codmsprod.b2clogin.com/adb2codmsprod.onmicrosoft.com/b2c_1_signin_prod
VITE_B2C_KNOWNAUTHORITIES=adb2codmsprod.b2clogin.com
VITE_DESK_TOP_APP_SCHEME=odms-desktopapp

View File

@ -1,5 +1,5 @@
VITE_STAGE=staging
VITE_B2C_CLIENTID=6ddb8ca0-c39e-4eba-a3c1-d18ea289a315
VITE_B2C_AUTHORITY=https://adb2codmsstaging.b2clogin.com/adb2codmsstaging.onmicrosoft.com/b2c_1_signin_staging
VITE_B2C_KNOWNAUTHORITIES=adb2codmsstaging.b2clogin.com
VITE_B2C_CLIENTID=5d8f0db9-4506-41d6-a5bb-5ec39f6eba8d
VITE_B2C_AUTHORITY=https://adb2codmsstg.b2clogin.com/adb2codmsstg.onmicrosoft.com/b2c_1_signin_stg
VITE_B2C_KNOWNAUTHORITIES=adb2codmsstg.b2clogin.com
VITE_DESK_TOP_APP_SCHEME=odms-desktopapp

View File

@ -27,7 +27,6 @@ module.exports = {
rules: {
"react/jsx-uses-react": "off",
"react/react-in-jsx-scope": "off",
"react/require-default-props": "off",
"react/function-component-definition": [
"error",
{

View File

@ -1,5 +0,0 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};

File diff suppressed because it is too large Load Diff

View File

@ -15,8 +15,7 @@
"typecheck": "tsc --noEmit",
"codegen": "sh codegen.sh",
"lint": "eslint --cache . --ext .js,.ts,.tsx",
"lint:fix": "npm run lint -- --fix",
"test": "jest"
"lint:fix": "npm run lint -- --fix"
},
"dependencies": {
"@azure/msal-browser": "^2.33.0",
@ -26,6 +25,7 @@
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^14.2.1",
"@types/jest": "^27.5.2",
"@types/node": "^17.0.45",
"@types/react": "^18.0.14",
"@types/react-dom": "^18.0.6",
@ -38,7 +38,6 @@
"jwt-decode": "^3.1.2",
"lodash": "^4.17.21",
"luxon": "^3.3.0",
"papaparse": "^5.4.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-google-recaptcha-v3": "^1.10.0",
@ -57,10 +56,8 @@
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
"@mdx-js/react": "^2.1.2",
"@openapitools/openapi-generator-cli": "^2.5.2",
"@types/jest": "^29.5.12",
"@types/lodash": "^4.14.191",
"@types/luxon": "^3.2.0",
"@types/papaparse": "^5.3.14",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@types/redux-mock-store": "^1.0.3",
@ -70,18 +67,16 @@
"babel-loader": "^8.2.5",
"eslint": "^8.19.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.10.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.6.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.30.1",
"eslint-plugin-react-hooks": "^4.6.0",
"jest": "^29.7.0",
"license-checker": "^25.0.1",
"prettier": "^2.8.8",
"prettier": "^2.7.1",
"redux-mock-store": "^1.5.4",
"sass": "^1.58.3",
"ts-jest": "^29.1.2",
"typescript": "^4.7.4",
"vite": "^4.1.4",
"vite-plugin-env-compatible": "^1.1.1",
@ -104,4 +99,4 @@
}
]
}
}
}

View File

@ -11,29 +11,13 @@ import { selectSnackber } from "features/ui/selectors";
import { closeSnackbar } from "features/ui/uiSlice";
import { UNAUTHORIZED_TO_CONTINUE_ERROR_CODES } from "components/auth/constants";
import { clearUserInfo } from "features/login";
import { UpdateTokenTimer } from "components/auth/updateTokenTimer";
/*
UpdateTokenTimerをApp.tsxに移動する2024627
App.tsxに移動する
*/
const App = (): JSX.Element => {
const dispatch = useDispatch();
const { instance } = useMsal();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [t, i18n] = useTranslation();
useEffect(() => {
// すべてのリクエストのヘッダーにX-Requested-Withを追加
globalAxios.interceptors.request.use((config) => {
// headersがあれば追加、なければ新規作成
config.headers = config.headers || {};
// X-Requested-Withを追加
config.headers["X-Requested-With"] = "XMLHttpRequest";
return config;
});
const id = globalAxios.interceptors.response.use(
(response: AxiosResponse) => response,
(e: AxiosError<{ code?: string }>) => {
@ -89,7 +73,6 @@ const App = (): JSX.Element => {
/>
<BrowserRouter>
<AppRouter />
<UpdateTokenTimer />
</BrowserRouter>
</>
);

File diff suppressed because it is too large Load Diff

View File

@ -10,8 +10,6 @@ import licenseCardIssue from "features/license/licenseCardIssue/licenseCardIssue
import licenseCardActivate from "features/license/licenseCardActivate/licenseCardActivateSlice";
import licenseSummary from "features/license/licenseSummary/licenseSummarySlice";
import partnerLicense from "features/license/partnerLicense/partnerLicenseSlice";
import licenseTrialIssue from "features/license/licenseTrialIssue/licenseTrialIssueSlice";
import searchPartners from "features/license/searchPartner/searchPartnerSlice";
import dictation from "features/dictation/dictationSlice";
import partner from "features/partner/partnerSlice";
import licenseOrderHistory from "features/license/licenseOrderHistory/licenseOrderHistorySlice";
@ -37,8 +35,6 @@ export const store = configureStore({
licenseSummary,
licenseOrderHistory,
partnerLicense,
licenseTrialIssue,
searchPartners,
dictation,
partner,
typistGroup,

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 28.3.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="M24.1,38l5.7-5.7l-5.7-5.6L22,28.8l2.1,2.1c-0.9,0-1.8-0.1-2.7-0.4c-0.9-0.3-1.7-0.9-2.4-1.6
c-0.7-0.7-1.2-1.4-1.5-2.3C17.2,25.8,17,24.9,17,24c0-0.6,0.1-1.1,0.2-1.7s0.4-1.1,0.6-1.7l-2.2-2.2c-0.6,0.8-1,1.7-1.2,2.6
C14.1,22.1,14,23,14,24c0,1.3,0.2,2.5,0.8,3.8C15.2,29,16,30.1,17,31s2,1.7,3.2,2.2c1.2,0.5,2.4,0.7,3.7,0.8L22,35.9L24.1,38z
M32.4,29.5c0.6-0.8,1-1.7,1.2-2.6C33.9,25.9,34,25,34,24c0-1.3-0.2-2.5-0.7-3.8s-1.2-2.4-2.2-3.3s-2.1-1.7-3.3-2.2
c-1.2-0.5-2.5-0.7-3.7-0.7l1.9-1.9L23.9,10l-5.7,5.7l5.7,5.6l2.1-2.1L23.8,17c0.9,0,1.8,0.2,2.8,0.5s1.7,0.9,2.4,1.5
s1.2,1.4,1.5,2.3c0.4,0.9,0.5,1.7,0.5,2.6c0,0.6-0.1,1.1-0.2,1.7c-0.1,0.6-0.4,1.1-0.6,1.6L32.4,29.5z M24,44
c-2.7,0-5.3-0.5-7.8-1.6s-4.6-2.5-6.4-4.3s-3.2-3.9-4.3-6.4S4,26.7,4,24c0-2.8,0.5-5.4,1.6-7.8s2.5-4.5,4.3-6.3s3.9-3.2,6.4-4.3
S21.3,4,24,4c2.8,0,5.4,0.5,7.8,1.6s4.6,2.5,6.4,4.3s3.2,3.9,4.3,6.3c1.1,2.4,1.6,5,1.6,7.8c0,2.7-0.5,5.3-1.6,7.8
c-1,2.4-2.5,4.6-4.3,6.4s-3.9,3.2-6.4,4.3S26.8,44,24,44z M24,41c4.7,0,8.8-1.7,12-5c3.3-3.3,5-7.3,5-12c0-4.7-1.6-8.8-5-12.1
c-3.3-3.3-7.3-5-12-5c-4.7,0-8.7,1.7-12,5S7,19.3,7,24c0,4.7,1.7,8.7,5,12C15.3,39.3,19.3,41,24,41z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48px" height="48px" viewBox="0 0 48 48" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,15.686275%,15.686275%);fill-opacity:1;" d="M 42.949219 37.109375 L 33.898438 28.0625 L 33.007812 28.949219 L 31.476562 27.414062 C 33.183594 24.882812 34.046875 21.945312 34.042969 19.011719 C 34.046875 15.171875 32.578125 11.320312 29.644531 8.390625 C 26.722656 5.464844 22.867188 4 19.019531 4.003906 C 15.179688 4 11.324219 5.46875 8.398438 8.390625 C 5.46875 11.320312 4 15.167969 4.003906 19.011719 C 4 22.851562 5.46875 26.703125 8.394531 29.628906 C 11.328125 32.554688 15.179688 34.019531 19.023438 34.019531 C 21.957031 34.019531 24.902344 33.160156 27.433594 31.453125 L 28.96875 32.988281 L 28.082031 33.871094 L 37.136719 42.917969 C 37.847656 43.636719 38.796875 43.996094 39.738281 43.996094 C 40.671875 43.996094 41.621094 43.636719 42.335938 42.917969 L 42.949219 42.308594 C 43.664062 41.59375 44.027344 40.644531 44.027344 39.707031 C 44.027344 38.769531 43.664062 37.824219 42.949219 37.109375 Z M 19.023438 32.003906 C 15.6875 32.003906 12.359375 30.738281 9.824219 28.199219 C 7.285156 25.667969 6.019531 22.34375 6.019531 19.011719 C 6.019531 15.675781 7.289062 12.351562 9.824219 9.820312 C 12.359375 7.285156 15.6875 6.019531 19.019531 6.019531 C 22.355469 6.019531 25.683594 7.285156 28.21875 9.820312 C 30.757812 12.351562 32.023438 15.675781 32.027344 19.011719 C 32.023438 22.34375 30.757812 25.667969 28.222656 28.199219 C 25.683594 30.738281 22.355469 32.003906 19.023438 32.003906 Z M 28.78125 30.421875 C 29.074219 30.171875 29.367188 29.910156 29.648438 29.628906 C 29.929688 29.351562 30.191406 29.058594 30.445312 28.761719 L 31.820312 30.136719 L 30.15625 31.800781 Z M 41.523438 40.882812 L 40.910156 41.492188 C 40.582031 41.820312 40.164062 41.976562 39.734375 41.980469 C 39.308594 41.980469 38.890625 41.820312 38.5625 41.496094 L 30.9375 33.875 L 33.898438 30.917969 L 41.523438 38.535156 C 41.847656 38.863281 42.007812 39.28125 42.007812 39.707031 C 42.007812 40.136719 41.847656 40.554688 41.523438 40.882812 Z M 41.523438 40.882812 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,15.686275%,15.686275%);fill-opacity:1;" d="M 25.695312 12.34375 C 23.855469 10.507812 21.433594 9.585938 19.023438 9.585938 C 16.609375 9.585938 14.191406 10.507812 12.351562 12.34375 C 10.511719 14.179688 9.589844 16.601562 9.59375 19.011719 C 9.589844 21.421875 10.511719 23.84375 12.351562 25.675781 C 14.191406 27.511719 16.609375 28.433594 19.019531 28.433594 C 21.433594 28.433594 23.855469 27.511719 25.695312 25.675781 C 27.535156 23.839844 28.453125 21.421875 28.453125 19.011719 C 28.457031 16.601562 27.53125 14.183594 25.695312 12.34375 Z M 24.503906 24.488281 C 22.992188 25.996094 21.011719 26.753906 19.019531 26.75 C 17.03125 26.75 15.050781 25.996094 13.539062 24.488281 C 12.027344 22.976562 11.277344 21 11.277344 19.011719 C 11.277344 17.023438 12.027344 15.042969 13.539062 13.53125 C 15.054688 12.023438 17.03125 11.269531 19.023438 11.265625 C 21.011719 11.269531 22.992188 12.023438 24.503906 13.53125 C 26.015625 15.042969 26.769531 17.023438 26.769531 19.011719 C 26.769531 21 26.015625 22.976562 24.503906 24.488281 Z M 24.503906 24.488281 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="_レイヤー_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 37.17"><defs><style>.cls-1{fill:#282828;}.cls-1,.cls-2{stroke-width:0px;}.cls-2{fill:#e6e6e6;}</style></defs><path class="cls-2" d="M42.13,35.07l-2.15,2.1L3,5.1v6.1H0V0h11.15v3h-6l36.98,32.07Z"/><path class="cls-1" d="M39.98,37.17l-2.1-2.15L74.9,3h-6.1V0h11.2v11.15h-3v-6l-37.03,32.02Z"/></svg>

Before

Width:  |  Height:  |  Size: 409 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M450-313v-371L330-564l-43-43 193-193 193 193-43 43-120-120v371h-60ZM220-160q-24 0-42-18t-18-42v-143h60v143h520v-143h60v143q0 24-18 42t-42 18H220Z"/></svg>

Before

Width:  |  Height:  |  Size: 251 B

View File

@ -54,7 +54,6 @@ export const errorCodes = [
"E010809", // ライセンス発行キャンセル不可エラー(ステータスが変えられている場合)
"E010810", // ライセンス発行キャンセル不可エラー(発行から一定期間経過した場合)
"E010811", // ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合)
"E010812", // ライセンス未割当エラー
"E010908", // タイピストグループ不在エラー
"E010909", // タイピストグループ名重複エラー
"E011001", // ワークタイプ重複エラー
@ -63,26 +62,4 @@ export const errorCodes = [
"E011004", // ワークタイプ使用中エラー
"E013001", // ワークフローのAuthorIDとWorktypeIDのペア重複エラー
"E013002", // ワークフロー不在エラー
"E014001", // ユーザー削除エラー(削除しようとしたユーザーがすでに削除済みだった)
"E014002", // ユーザー削除エラー(削除しようとしたユーザーが管理者だった)
"E014003", // ユーザー削除エラー削除しようとしたAuthorのAuthorIDがWorkflowに指定されていた
"E014004", // ユーザー削除エラー削除しようとしたTypistがWorkflowのTypist候補として指定されていた
"E014005", // ユーザー削除エラー削除しようとしたTypistがUserGroupに所属していた
"E014006", // ユーザー削除エラー(削除しようとしたユーザが所有者の未完了のタスクが残っている)
"E014007", // ユーザー削除エラー(削除しようとしたユーザーが有効なライセンスを持っていた)
"E014009", // ユーザー削除エラー削除しようとしたTypistが未完了のタスクのルーティングに設定されている
"E015001", // タイピストグループ削除済みエラー
"E015002", // タイピストグループがワークフローに紐づいているエラー
"E015003", // タイピストグループがルーティングされているエラー
"E016001", // テンプレートファイル削除エラー(削除しようとしたテンプレートファイルがすでに削除済みだった)
"E016002", // テンプレートファイル削除エラー削除しようとしたテンプレートファイルがWorkflowに指定されていた
"E016003", // テンプレートファイル削除エラー(削除しようとしたテンプレートファイルが未完了のタスクに紐づいていた)
"E017001", // 親アカウント変更不可エラー(指定したアカウントが存在しない)
"E017002", // 親アカウント変更不可エラー(階層関係が不正)
"E017003", // 親アカウント変更不可エラー(リージョンが同一でない)
"E018001", // パートナーアカウント削除エラー(削除条件を満たしていない)
"E019001", // パートナーアカウント取得不可エラー(階層構造が不正)
"E020001", // パートナーアカウント変更エラー(変更条件を満たしていない)
"E021001", // 音声ファイル名変更不可エラー(権限不足)
"E021002", // 音声ファイル名変更不可エラー(同名ファイルが存在)
] as const;

View File

@ -6,4 +6,4 @@ export type ErrorObject = {
statusCode?: number;
};
export type ErrorCodeType = (typeof errorCodes)[number];
export type ErrorCodeType = typeof errorCodes[number];

View File

@ -1,153 +0,0 @@
/* eslint-disable @typescript-eslint/naming-convention */
// Jestによるparser.tsのテスト
import fs from "fs";
import { CSVType, parseCSV } from "./parser";
describe("parse", () => {
it("指定形式のCSV文字列をパースできる", async () => {
const text = fs.readFileSync("src/common/test/test_001.csv", "utf-8");
const actualData = await parseCSV(text);
const expectData: CSVType[] = [
{
name: "hoge",
email: "sample@example.com",
role: 1,
author_id: "HOGE",
auto_assign: 1,
notification: 1,
encryption: 1,
encryption_password: "abcd",
prompt: 0,
},
];
expect(actualData).toEqual(expectData);
});
it("指定形式のヘッダでない場合、例外が送出される | author_id(値がoptionial)がない", async () => {
const text = fs.readFileSync("src/common/test/test_002.csv", "utf-8");
try {
await parseCSV(text);
fail("例外が発生しませんでした");
} catch (e) {
expect(e).toEqual(new Error("Invalid CSV format"));
}
});
it("指定形式のヘッダでない場合、例外が送出される | email(値が必須)がない", async () => {
const text = fs.readFileSync("src/common/test/test_003.csv", "utf-8");
try {
await parseCSV(text);
fail("例外が発生しませんでした");
} catch (e) {
expect(e).toEqual(new Error("Invalid CSV format"));
}
});
it("指定形式のヘッダでない場合、例外が送出される | emailがスペルミス", async () => {
const text = fs.readFileSync("src/common/test/test_004.csv", "utf-8");
try {
await parseCSV(text);
fail("例外が発生しませんでした");
} catch (e) {
expect(e).toEqual(new Error("Invalid CSV format"));
}
});
it("指定形式のCSV文字列をパースできる | 抜けているパラメータ(文字列)はnullとなる", async () => {
const text = fs.readFileSync("src/common/test/test_005.csv", "utf-8");
const actualData = await parseCSV(text);
const expectData: CSVType[] = [
{
name: "hoge",
email: "sample@example.com",
role: 1,
author_id: null,
auto_assign: 1,
notification: 1,
encryption: 1,
encryption_password: "abcd",
prompt: 0,
},
];
expect(actualData).toEqual(expectData);
});
it("指定形式のCSV文字列をパースできる | 抜けているパラメータ(数値)はnullとなる", async () => {
const text = fs.readFileSync("src/common/test/test_006.csv", "utf-8");
const actualData = await parseCSV(text);
const expectData: CSVType[] = [
{
name: "hoge",
email: "sample@example.com",
role: null,
author_id: "HOGE",
auto_assign: 1,
notification: 1,
encryption: 1,
encryption_password: "abcd",
prompt: 0,
},
];
expect(actualData).toEqual(expectData);
});
it("指定形式のCSV文字列をパースできる | 余計なパラメータがあっても問題はない", async () => {
const text = fs.readFileSync("src/common/test/test_007.csv", "utf-8");
const actualData = await parseCSV(text);
const expectData: CSVType[] = [
{
name: "hoge",
email: "sample@example.com",
role: 1,
author_id: "HOGE",
auto_assign: 1,
notification: 1,
encryption: 1,
encryption_password: "abcd",
prompt: 0,
},
{
name: "hoge2",
email: "sample2@example.com",
role: 1,
author_id: "HOGE2",
auto_assign: 1,
notification: 1,
encryption: 1,
encryption_password: "abcd2",
prompt: 0,
},
];
expect(actualData.length).toBe(expectData.length);
// 余計なパラメータ格納用に __parsed_extra: string[] というプロパティが作られてしまうので、既知のプロパティ毎に比較
for (let i = 0; i < actualData.length; i += 1) {
const actualValue = actualData[i];
const expectValue = expectData[i];
expect(actualValue.author_id).toEqual(expectValue.author_id);
expect(actualValue.auto_assign).toEqual(expectValue.auto_assign);
expect(actualValue.email).toEqual(expectValue.email);
expect(actualValue.encryption).toEqual(expectValue.encryption);
expect(actualValue.encryption_password).toEqual(
expectValue.encryption_password
);
expect(actualValue.name).toEqual(expectValue.name);
expect(actualValue.notification).toEqual(expectValue.notification);
expect(actualValue.prompt).toEqual(expectValue.prompt);
expect(actualValue.role).toEqual(expectValue.role);
}
});
it("author_id,encryption_passwordが数値のみの場合でも、文字列として変換できる", async () => {
const text = fs.readFileSync("src/common/test/test_008.csv", "utf-8");
const actualData = await parseCSV(text);
const expectData: CSVType[] = [
{
name: "hoge",
email: "sample@example.com",
role: 1,
author_id: "1111",
auto_assign: 1,
notification: 1,
encryption: 1,
encryption_password: "222222",
prompt: 0,
},
];
expect(actualData).toEqual(expectData);
});
});

View File

@ -1,74 +0,0 @@
/* eslint-disable @typescript-eslint/naming-convention */
import Papa, { ParseResult } from "papaparse";
export type CSVType = {
name: string | null;
email: string | null;
role: number | null;
author_id: string | null;
auto_assign: number | null;
notification: number;
encryption: number | null;
encryption_password: string | null;
prompt: number | null;
};
// CSVTypeのプロパティ名を文字列の配列で定義する
const CSVTypeFields: (keyof CSVType)[] = [
"name",
"email",
"role",
"author_id",
"auto_assign",
"notification",
"encryption",
"encryption_password",
"prompt",
];
// 2つの配列が等しいかどうかを判定する
const equals = (lhs: string[], rhs: string[]) => {
if (lhs.length !== rhs.length) return false;
for (let i = 0; i < lhs.length; i += 1) {
if (lhs[i] !== rhs[i]) return false;
}
return true;
};
/** CSVファイルをCSVType型に変換するパーサー */
export const parseCSV = async (csvString: string): Promise<CSVType[]> =>
new Promise((resolve, reject) => {
Papa.parse<CSVType>(csvString, {
download: false,
worker: false, // XXX: workerを使うとエラーが発生するためfalseに設定
header: true,
dynamicTyping: {
// author_id, encryption_passwordは数値のみの場合、numberに変換されたくないためdynamicTypingをtrueにしない
role: true,
auto_assign: true,
notification: true,
encryption: true,
prompt: true,
},
// dynamicTypingがfalseの場合、空文字をnullに変換できないためtransformを使用する
transform: (value, field) => {
if (field === "author_id" || field === "encryption_password") {
// 空文字の場合はnullに変換する
if (value === "") {
return null;
}
}
return value;
},
complete: (results: ParseResult<CSVType>) => {
// ヘッダーがCSVTypeFieldsと一致しない場合はエラーを返す
if (!equals(results.meta.fields ?? [], CSVTypeFields)) {
reject(new Error("Invalid CSV format"));
}
resolve(results.data);
},
error: (error: Error) => {
reject(error);
},
});
});

View File

@ -1,2 +0,0 @@
name,email,role,author_id,auto_assign,notification,encryption,encryption_password,prompt
hoge,sample@example.com,1,"HOGE",1,1,1,abcd,0
1 name email role author_id auto_assign notification encryption encryption_password prompt
2 hoge sample@example.com 1 HOGE 1 1 1 abcd 0

View File

@ -1,2 +0,0 @@
name,email,role,auto_assign,notification,encryption,encryption_password,prompt
hoge,sample@example.com,1,"HOGE",1,1,1,abcd,0
Can't render this file because it has a wrong number of fields in line 2.

View File

@ -1,2 +0,0 @@
name,role,author_id,auto_assign,notification,encryption,encryption_password,prompt
hoge,sample@example.com,1,"HOGE",1,1,1,abcd,0
Can't render this file because it has a wrong number of fields in line 2.

View File

@ -1,2 +0,0 @@
name,emeil,role,author_id,auto_assign,notification,encryption,encryption_password,prompt
hoge,sample@example.com,1,"HOGE",1,1,1,abcd,0
1 name emeil role author_id auto_assign notification encryption encryption_password prompt
2 hoge sample@example.com 1 HOGE 1 1 1 abcd 0

View File

@ -1,2 +0,0 @@
name,email,role,author_id,auto_assign,notification,encryption,encryption_password,prompt
hoge,sample@example.com,1,,1,1,1,abcd,0
1 name email role author_id auto_assign notification encryption encryption_password prompt
2 hoge sample@example.com 1 1 1 1 abcd 0

View File

@ -1,2 +0,0 @@
name,email,role,author_id,auto_assign,notification,encryption,encryption_password,prompt
hoge,sample@example.com,,"HOGE",1,1,1,abcd,0
1 name email role author_id auto_assign notification encryption encryption_password prompt
2 hoge sample@example.com HOGE 1 1 1 abcd 0

View File

@ -1,3 +0,0 @@
name,email,role,author_id,auto_assign,notification,encryption,encryption_password,prompt
hoge,sample@example.com,1,"HOGE",1,1,1,abcd,0,x
hoge2,sample2@example.com,1,"HOGE2",1,1,1,abcd2,0,1,32,4,aa
Can't render this file because it has a wrong number of fields in line 2.

View File

@ -1,2 +0,0 @@
name,email,role,author_id,auto_assign,notification,encryption,encryption_password,prompt
hoge,sample@example.com,1,1111,1,1,1,222222,0
1 name email role author_id auto_assign notification encryption encryption_password prompt
2 hoge sample@example.com 1 1111 1 1 1 222222 0

View File

@ -47,7 +47,6 @@ export const KEYS_TO_PRESERVE = [
"accessToken",
"refreshToken",
"displayInfo",
"filterCriteria",
"sortCriteria",
];

View File

@ -1,4 +1,4 @@
import React, { useCallback, useLayoutEffect, useState } from "react";
import React, { useCallback } from "react";
import { AppDispatch } from "app/store";
import { decodeToken } from "common/decodeToken";
import { useInterval } from "common/useInterval";
@ -17,58 +17,41 @@ import { TOKEN_UPDATE_INTERVAL_MS, TOKEN_UPDATE_TIME } from "./constants";
export const UpdateTokenTimer = () => {
const dispatch: AppDispatch = useDispatch();
const navigate = useNavigate();
// トークンの更新中かどうか
const [isUpdating, setIsUpdating] = useState(false);
const delegattionToken = useSelector(selectDelegationAccessToken);
// 期限が分以内であれば更新APIを呼ぶ
const updateToken = useCallback(async () => {
if (isUpdating) {
return;
}
setIsUpdating(true);
try {
// localStorageからトークンを取得
const jwt = loadAccessToken();
// 現在時刻を取得
const now = DateTime.local().toSeconds();
// selectorに以下の判定処理を移したかったが、初期表示時の値でしか判定できないのでComponent内に置く
if (jwt) {
const token = decodeToken(jwt);
if (token) {
const { exp } = token;
if (exp - now <= TOKEN_UPDATE_TIME) {
await dispatch(updateTokenAsync());
}
// localStorageからトークンを取得
const jwt = loadAccessToken();
// 現在時刻を取得
const now = DateTime.local().toSeconds();
// selectorに以下の判定処理を移したかったが、初期表示時の値でしか判定できないのでComponent内に置く
if (jwt) {
const token = decodeToken(jwt);
if (token) {
const { exp } = token;
if (exp - now <= TOKEN_UPDATE_TIME) {
await dispatch(updateTokenAsync());
}
}
}
// 代行操作トークン更新処理
if (delegattionToken) {
const token = decodeToken(delegattionToken);
if (token) {
const { exp } = token;
if (exp - now <= TOKEN_UPDATE_TIME) {
const { meta } = await dispatch(updateDelegationTokenAsync());
if (meta.requestStatus === "rejected") {
dispatch(cleanupDelegateAccount());
navigate("/partners");
}
// 代行操作トークン更新処理
if (delegattionToken) {
const token = decodeToken(delegattionToken);
if (token) {
const { exp } = token;
if (exp - now <= TOKEN_UPDATE_TIME) {
const { meta } = await dispatch(updateDelegationTokenAsync());
if (meta.requestStatus === "rejected") {
dispatch(cleanupDelegateAccount());
navigate("/partners");
}
}
}
} catch (e) {
// eslint-disable-next-line no-console
console.error("Token update error:", e);
} finally {
setIsUpdating(false);
}
}, [isUpdating, delegattionToken, dispatch, navigate]);
useLayoutEffect(() => {
updateToken();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [dispatch, delegattionToken, navigate]);
useInterval(updateToken, TOKEN_UPDATE_INTERVAL_MS);

View File

@ -6,50 +6,8 @@ import { getTranslationID } from "translation";
const Footer: React.FC = () => {
const [t] = useTranslation();
return (
<footer
className={`${styles.footer}`}
style={{
padding: "0.5rem",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<div
style={{
marginBottom: "0.5rem",
textAlign: "center",
}}
>
{t(getTranslationID("common.label.copyRight"))}
</div>
<div
style={{
display: "flex",
gap: "1rem",
}}
>
<a
href="https://download.omsystem.com/pages/odms_download/odms_cloud_eula/eula/en/"
target="_blank"
className={styles.linkTx}
style={{ color: "#999999" }}
data-tag="open-eula"
rel="noreferrer"
>
{t(getTranslationID("common.label.eula"))}
</a>
<a
href="https://download.omsystem.com/pages/odms_download/odms_cloud_eula/privacy_notice/en/"
target="_blank"
className={styles.linkTx}
style={{ color: "#999999" }}
data-tag="open-privacy-notice"
rel="noreferrer"
>
{t(getTranslationID("common.label.privacyNotice"))}
</a>
</div>
<footer className={`${styles.footer}`}>
<div>{t(getTranslationID("common.label.copyRight"))}</div>
</footer>
);
};

View File

@ -4,7 +4,6 @@ import {
updateAccountInfoAsync,
getAccountRelationsAsync,
deleteAccountAsync,
updateFileDeleteSettingAsync,
} from "./operations";
const initialState: AccountState = {
@ -16,8 +15,6 @@ const initialState: AccountState = {
tier: 0,
country: "",
delegationPermission: false,
autoFileDelete: false,
fileRetentionDays: 0,
},
},
dealers: [],
@ -32,8 +29,6 @@ const initialState: AccountState = {
secondryAdminUserId: undefined,
},
isLoading: false,
autoFileDelete: false,
fileRetentionDays: 0,
},
};
@ -69,20 +64,6 @@ export const accountSlice = createSlice({
const { secondryAdminUserId } = action.payload;
state.apps.updateAccountInfo.secondryAdminUserId = secondryAdminUserId;
},
changeAutoFileDelete: (
state,
action: PayloadAction<{ autoFileDelete: boolean }>
) => {
const { autoFileDelete } = action.payload;
state.apps.autoFileDelete = autoFileDelete;
},
changeFileRetentionDays: (
state,
action: PayloadAction<{ fileRetentionDays: number }>
) => {
const { fileRetentionDays } = action.payload;
state.apps.fileRetentionDays = fileRetentionDays;
},
cleanupApps: (state) => {
state.domain = initialState.domain;
},
@ -104,10 +85,6 @@ export const accountSlice = createSlice({
action.payload.accountInfo.account.primaryAdminUserId;
state.apps.updateAccountInfo.secondryAdminUserId =
action.payload.accountInfo.account.secondryAdminUserId;
state.apps.autoFileDelete =
action.payload.accountInfo.account.autoFileDelete;
state.apps.fileRetentionDays =
action.payload.accountInfo.account.fileRetentionDays;
state.apps.isLoading = false;
});
builder.addCase(getAccountRelationsAsync.rejected, (state) => {
@ -122,15 +99,6 @@ export const accountSlice = createSlice({
builder.addCase(updateAccountInfoAsync.rejected, (state) => {
state.apps.isLoading = false;
});
builder.addCase(updateFileDeleteSettingAsync.pending, (state) => {
state.apps.isLoading = true;
});
builder.addCase(updateFileDeleteSettingAsync.fulfilled, (state) => {
state.apps.isLoading = false;
});
builder.addCase(updateFileDeleteSettingAsync.rejected, (state) => {
state.apps.isLoading = false;
});
builder.addCase(deleteAccountAsync.pending, (state) => {
state.apps.isLoading = true;
});
@ -147,8 +115,6 @@ export const {
changeDealerPermission,
changePrimaryAdministrator,
changeSecondryAdministrator,
changeAutoFileDelete,
changeFileRetentionDays,
cleanupApps,
} = accountSlice.actions;
export default accountSlice.reducer;

View File

@ -9,7 +9,6 @@ import {
UpdateAccountInfoRequest,
UsersApi,
DeleteAccountRequest,
UpdateFileDeleteSettingRequest,
} from "../../api/api";
import { Configuration } from "../../api/configuration";
import { ViewAccountRelationsInfo } from "./types";
@ -39,7 +38,7 @@ export const getAccountRelationsAsync = createAsyncThunk<
headers: { authorization: `Bearer ${accessToken}` },
});
const dealers = await accountsApi.getDealers();
const users = await usersApi.getUsers(undefined, undefined, {
const users = await usersApi.getUsers({
headers: { authorization: `Bearer ${accessToken}` },
});
return {
@ -113,58 +112,6 @@ export const updateAccountInfoAsync = createAsyncThunk<
}
});
export const updateFileDeleteSettingAsync = createAsyncThunk<
{
/* Empty Object */
},
{ autoFileDelete: boolean; fileRetentionDays: number },
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("accounts/updateFileDeleteSettingAsync", async (args, thunkApi) => {
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration } = state.auth;
const accessToken = getAccessToken(state.auth);
const config = new Configuration(configuration);
const accountApi = new AccountsApi(config);
const requestParam: UpdateFileDeleteSettingRequest = {
autoFileDelete: args.autoFileDelete,
retentionDays: args.fileRetentionDays,
};
try {
await accountApi.updateFileDeleteSetting(requestParam, {
headers: { authorization: `Bearer ${accessToken}` },
});
thunkApi.dispatch(
openSnackbar({
level: "info",
message: getTranslationID("common.message.success"),
})
);
return {};
} catch (e) {
const error = createErrorObject(e);
const errorMessage = getTranslationID("common.message.internalServerError");
thunkApi.dispatch(
openSnackbar({
level: "error",
message: errorMessage,
})
);
return thunkApi.rejectWithValue({ error });
}
});
export const deleteAccountAsync = createAsyncThunk<
{
/* Empty Object */

View File

@ -16,18 +16,3 @@ export const selectIsLoading = (state: RootState) =>
state.account.apps.isLoading;
export const selectUpdateAccountInfo = (state: RootState) =>
state.account.apps.updateAccountInfo;
export const selectFileDeleteSetting = (state: RootState) => {
const { autoFileDelete, fileRetentionDays } = state.account.apps;
return {
autoFileDelete,
fileRetentionDays,
};
};
export const selectInputValidationErrors = (state: RootState) => {
const { fileRetentionDays } = state.account.apps;
const hasFileRetentionDaysError =
fileRetentionDays <= 0 || fileRetentionDays >= 1000;
return {
hasFileRetentionDaysError,
};
};

View File

@ -19,6 +19,4 @@ export interface Domain {
export interface Apps {
updateAccountInfo: UpdateAccountInfoRequest;
isLoading: boolean;
autoFileDelete: boolean;
fileRetentionDays: number;
}

View File

@ -6,7 +6,7 @@ export const STATUS = {
BACKUP: "Backup",
} as const;
export type StatusType = (typeof STATUS)[keyof typeof STATUS];
export type StatusType = typeof STATUS[keyof typeof STATUS];
export const LIMIT_TASK_NUM = 100;
@ -26,7 +26,7 @@ export const SORTABLE_COLUMN = {
TranscriptionFinishedDate: "TRANSCRIPTION_FINISHED_DATE",
} as const;
export type SortableColumnType =
(typeof SORTABLE_COLUMN)[keyof typeof SORTABLE_COLUMN];
typeof SORTABLE_COLUMN[keyof typeof SORTABLE_COLUMN];
export const isSortableColumnType = (
value: string
@ -36,14 +36,14 @@ export const isSortableColumnType = (
};
export type SortableColumnList =
(typeof SORTABLE_COLUMN)[keyof typeof SORTABLE_COLUMN];
typeof SORTABLE_COLUMN[keyof typeof SORTABLE_COLUMN];
export const DIRECTION = {
ASC: "ASC",
DESC: "DESC",
} as const;
export type DirectionType = (typeof DIRECTION)[keyof typeof DIRECTION];
export type DirectionType = typeof DIRECTION[keyof typeof DIRECTION];
// DirectionTypeの型チェック関数
export const isDirectionType = (arg: string): arg is DirectionType =>

View File

@ -11,8 +11,6 @@ import {
playbackAsync,
updateAssigneeAsync,
cancelAsync,
deleteTaskAsync,
renameFileAsync,
} from "./operations";
import {
SORTABLE_COLUMN,
@ -43,8 +41,6 @@ const initialState: DictationState = {
direction: DIRECTION.ASC,
paramName: SORTABLE_COLUMN.JobNumber,
selectedTask: undefined,
authorId: "",
fileName: "",
assignee: {
selected: [],
pool: [],
@ -80,14 +76,6 @@ export const dictationSlice = createSlice({
const { paramName } = action.payload;
state.apps.paramName = paramName;
},
changeAuthorId: (state, action: PayloadAction<{ authorId: string }>) => {
const { authorId } = action.payload;
state.apps.authorId = authorId;
},
changeFileName: (state, action: PayloadAction<{ fileName: string }>) => {
const { fileName } = action.payload;
state.apps.fileName = fileName;
},
changeSelectedTask: (state, action: PayloadAction<{ task: Task }>) => {
const { task } = action.payload;
state.apps.selectedTask = task;
@ -230,25 +218,6 @@ export const dictationSlice = createSlice({
builder.addCase(backupTasksAsync.rejected, (state) => {
state.apps.isDownloading = false;
});
builder.addCase(deleteTaskAsync.pending, (state) => {
state.apps.isLoading = true;
});
builder.addCase(deleteTaskAsync.fulfilled, (state) => {
state.apps.isLoading = false;
});
builder.addCase(deleteTaskAsync.rejected, (state) => {
state.apps.isLoading = false;
});
builder.addCase(renameFileAsync.pending, (state) => {
state.apps.isLoading = true;
});
builder.addCase(renameFileAsync.fulfilled, (state) => {
state.apps.isLoading = false;
});
builder.addCase(renameFileAsync.rejected, (state) => {
state.apps.isLoading = false;
});
},
});
@ -256,8 +225,6 @@ export const {
changeDisplayInfo,
changeDirection,
changeParamName,
changeAuthorId,
changeFileName,
changeSelectedTask,
changeAssignee,
changeBackupTaskChecked,

View File

@ -35,8 +35,6 @@ export const listTasksAsync = createAsyncThunk<
filter?: string;
direction: DirectionType;
paramName: SortableColumnType;
authorId?: string;
fileName?: string;
},
{
// rejectした時の返却値の型
@ -45,8 +43,7 @@ export const listTasksAsync = createAsyncThunk<
};
}
>("dictations/listTasksAsync", async (args, thunkApi) => {
const { limit, offset, filter, direction, paramName, authorId, fileName } =
args;
const { limit, offset, filter, direction, paramName } = args;
// apiのConfigurationを取得する
const { getState } = thunkApi;
@ -63,8 +60,6 @@ export const listTasksAsync = createAsyncThunk<
filter,
direction,
paramName,
authorId,
fileName,
{
headers: { authorization: `Bearer ${accessToken}` },
}
@ -85,136 +80,6 @@ export const listTasksAsync = createAsyncThunk<
}
});
export const getTaskFiltersAsync = createAsyncThunk<
{
authorId?: string;
fileName?: string;
},
void,
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("dictations/getTaskFiltersAsync", async (args, thunkApi) => {
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration } = state.auth;
const accessToken = getAccessToken(state.auth);
const config = new Configuration(configuration);
const usersApi = new UsersApi(config);
try {
const usertaskfilter = await usersApi.getTaskFilter({
headers: { authorization: `Bearer ${accessToken}` },
});
const { authorId, fileName } = usertaskfilter.data;
return { authorId, fileName };
} catch (e) {
// e ⇒ errorObjectに変換"
const error = createErrorObject(e);
thunkApi.dispatch(
openSnackbar({
level: "error",
message: getTranslationID("common.message.internalServerError"),
})
);
return thunkApi.rejectWithValue({ error });
}
});
export const updateTaskFiltersAsync = createAsyncThunk<
{
/** empty */
},
{
filterConditionAuthorId: string;
filterConditionFileName: string;
},
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("dictations/updateTaskFiltersAsync", async (args, thunkApi) => {
const { filterConditionAuthorId, filterConditionFileName } = args;
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration } = state.auth;
const accessToken = getAccessToken(state.auth);
const config = new Configuration(configuration);
const usersApi = new UsersApi(config);
try {
return await usersApi.updateTaskFilter(
{ filterConditionAuthorId, filterConditionFileName },
{
headers: { authorization: `Bearer ${accessToken}` },
}
);
} catch (e) {
// e ⇒ errorObjectに変換"
const error = createErrorObject(e);
thunkApi.dispatch(
openSnackbar({
level: "error",
message: getTranslationID("common.message.internalServerError"),
})
);
return thunkApi.rejectWithValue({ error });
}
});
export const updateSortColumnAsync = createAsyncThunk<
{
/** empty */
},
{
direction: DirectionType;
paramName: SortableColumnType;
},
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("dictations/updateSortColumnAsync", async (args, thunkApi) => {
const { direction, paramName } = args;
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration } = state.auth;
const accessToken = getAccessToken(state.auth);
const config = new Configuration(configuration);
const usersApi = new UsersApi(config);
try {
return await usersApi.updateSortCriteria(
{ direction, paramName },
{
headers: { authorization: `Bearer ${accessToken}` },
}
);
} catch (e) {
// e ⇒ errorObjectに変換"
const error = createErrorObject(e);
thunkApi.dispatch(
openSnackbar({
level: "error",
message: getTranslationID("common.message.internalServerError"),
})
);
return thunkApi.rejectWithValue({ error });
}
});
export const getSortColumnAsync = createAsyncThunk<
{
direction: DirectionType;
@ -415,8 +280,6 @@ export const playbackAsync = createAsyncThunk<
direction: DirectionType;
paramName: SortableColumnType;
audioFileId: number;
filterConditionAuthorId: string;
filterConditionFileName: string;
},
{
// rejectした時の返却値の型
@ -425,13 +288,7 @@ export const playbackAsync = createAsyncThunk<
};
}
>("dictations/playbackAsync", async (args, thunkApi) => {
const {
audioFileId,
direction,
paramName,
filterConditionAuthorId,
filterConditionFileName,
} = args;
const { audioFileId, direction, paramName } = args;
// apiのConfigurationを取得する
const { getState } = thunkApi;
@ -448,12 +305,6 @@ export const playbackAsync = createAsyncThunk<
headers: { authorization: `Bearer ${accessToken}` },
}
);
await usersApi.updateTaskFilter(
{ filterConditionAuthorId, filterConditionFileName },
{
headers: { authorization: `Bearer ${accessToken}` },
}
);
await tasksApi.checkout(audioFileId, {
headers: { authorization: `Bearer ${accessToken}` },
});
@ -492,30 +343,6 @@ export const playbackAsync = createAsyncThunk<
);
return thunkApi.rejectWithValue({ error });
}
// ライセンスの有効期限が切れている場合
if (error.code === "E010805") {
thunkApi.dispatch(
openSnackbar({
level: "error",
message: getTranslationID(
"dictationPage.message.licenseExpiredError"
),
})
);
return thunkApi.rejectWithValue({ error });
}
// ライセンスが未割当の場合
if (error.code === "E010812") {
thunkApi.dispatch(
openSnackbar({
level: "error",
message: getTranslationID(
"dictationPage.message.licenseNotAssignedError"
),
})
);
return thunkApi.rejectWithValue({ error });
}
thunkApi.dispatch(
openSnackbar({
@ -536,8 +363,6 @@ export const cancelAsync = createAsyncThunk<
paramName: SortableColumnType;
audioFileId: number;
isTypist: boolean;
filterConditionAuthorId: string;
filterConditionFileName: string;
},
{
// rejectした時の返却値の型
@ -546,14 +371,7 @@ export const cancelAsync = createAsyncThunk<
};
}
>("dictations/cancelAsync", async (args, thunkApi) => {
const {
audioFileId,
direction,
paramName,
isTypist,
filterConditionAuthorId,
filterConditionFileName,
} = args;
const { audioFileId, direction, paramName, isTypist } = args;
// apiのConfigurationを取得する
const { getState } = thunkApi;
@ -564,25 +382,15 @@ export const cancelAsync = createAsyncThunk<
const tasksApi = new TasksApi(config);
const usersApi = new UsersApi(config);
try {
// ユーザーがタイピストである場合に、ソート条件と検索条件を保存する
// ユーザーがタイピストである場合に、ソート条件を保存する
if (isTypist) {
await usersApi.updateSortCriteria(
{
direction,
paramName,
},
{
headers: { authorization: `Bearer ${accessToken}` },
}
);
await usersApi.updateTaskFilter(
{ filterConditionAuthorId, filterConditionFileName },
{ direction, paramName },
{
headers: { authorization: `Bearer ${accessToken}` },
}
);
}
await tasksApi.cancel(audioFileId, {
headers: { authorization: `Bearer ${accessToken}` },
});
@ -618,93 +426,6 @@ export const cancelAsync = createAsyncThunk<
}
});
export const reopenAsync = createAsyncThunk<
{
/** empty */
},
{
direction: DirectionType;
paramName: SortableColumnType;
audioFileId: number;
isTypist: boolean;
filterConditionAuthorId: string;
filterConditionFileName: string;
},
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("dictations/reopenAsync", async (args, thunkApi) => {
const {
audioFileId,
direction,
paramName,
isTypist,
filterConditionAuthorId,
filterConditionFileName,
} = args;
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration } = state.auth;
const accessToken = getAccessToken(state.auth);
const config = new Configuration(configuration);
const tasksApi = new TasksApi(config);
const usersApi = new UsersApi(config);
try {
// ユーザーがタイピストである場合に、ソート条件と検索条件を保存する
if (isTypist) {
await usersApi.updateSortCriteria(
{ direction, paramName },
{
headers: { authorization: `Bearer ${accessToken}` },
}
);
await usersApi.updateTaskFilter(
{ filterConditionAuthorId, filterConditionFileName },
{
headers: { authorization: `Bearer ${accessToken}` },
}
);
}
await tasksApi.reopen(audioFileId, {
headers: { authorization: `Bearer ${accessToken}` },
});
thunkApi.dispatch(
openSnackbar({
level: "info",
message: getTranslationID("common.message.success"),
})
);
return {};
} catch (e) {
// e ⇒ errorObjectに変換"
const error = createErrorObject(e);
// ステータスが[Finished]以外、またはタスクが存在しない場合、またはtypistで自分のタスクでない場合
if (error.code === "E010601" || error.code === "E010603") {
thunkApi.dispatch(
openSnackbar({
level: "error",
message: getTranslationID("dictationPage.message.reopenFailedError"),
})
);
return thunkApi.rejectWithValue({ error });
}
thunkApi.dispatch(
openSnackbar({
level: "error",
message: getTranslationID("common.message.internalServerError"),
})
);
return thunkApi.rejectWithValue({ error });
}
});
export const listBackupPopupTasksAsync = createAsyncThunk<
TasksResponse,
{
@ -735,8 +456,6 @@ export const listBackupPopupTasksAsync = createAsyncThunk<
BACKUP_POPUP_LIST_STATUS.join(","), // ステータスはFinished,Backupのみ
DIRECTION.DESC,
SORTABLE_COLUMN.Status,
undefined, // backupポップアップ表示時には検索条件は未指定
undefined, // backupポップアップ表示時には検索条件は未指定
{
headers: { authorization: `Bearer ${accessToken}` },
}
@ -822,21 +541,10 @@ export const backupTasksAsync = createAsyncThunk<
a.click();
a.parentNode?.removeChild(a);
// バックアップ済みに更新
try {
// eslint-disable-next-line no-await-in-loop
await tasksApi.backup(task.audioFileId, {
headers: { authorization: `Bearer ${accessToken}` },
});
} catch (e) {
// e ⇒ errorObjectに変換
const error = createErrorObject(e);
if (error.code === "E010603") {
// タスクが削除済みの場合は成功扱いとする
} else {
throw e;
}
}
// eslint-disable-next-line no-await-in-loop
await tasksApi.backup(task.audioFileId, {
headers: { authorization: `Bearer ${accessToken}` },
});
}
}
@ -848,22 +556,8 @@ export const backupTasksAsync = createAsyncThunk<
);
return {};
} catch (e) {
// e ⇒ errorObjectに変換
// e ⇒ errorObjectに変換"
const error = createErrorObject(e);
if (error.code === "E010603") {
// 存在しない音声ファイルをダウンロードしようとした場合
thunkApi.dispatch(
openSnackbar({
level: "error",
message: getTranslationID(
"dictationPage.message.fileAlreadyDeletedError"
),
})
);
return thunkApi.rejectWithValue({ error });
}
thunkApi.dispatch(
openSnackbar({
level: "error",
@ -874,143 +568,3 @@ export const backupTasksAsync = createAsyncThunk<
return thunkApi.rejectWithValue({ error });
}
});
export const deleteTaskAsync = createAsyncThunk<
{
// empty
},
{
// パラメータ
audioFileId: number;
},
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("dictations/deleteTaskAsync", async (args, thunkApi) => {
const { audioFileId } = args;
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration } = state.auth;
const accessToken = getAccessToken(state.auth);
const config = new Configuration(configuration);
const tasksApi = new TasksApi(config);
try {
await tasksApi.deleteTask(audioFileId, {
headers: { authorization: `Bearer ${accessToken}` },
});
thunkApi.dispatch(
openSnackbar({
level: "info",
message: getTranslationID("common.message.success"),
})
);
return {};
} catch (e) {
// e ⇒ errorObjectに変換"
const error = createErrorObject(e);
let message = getTranslationID("common.message.internalServerError");
if (error.statusCode === 400) {
if (error.code === "E010603") {
// タスクが削除済みの場合は成功扱いとする
thunkApi.dispatch(
openSnackbar({
level: "info",
message: getTranslationID("common.message.success"),
})
);
return {};
}
if (error.code === "E010601") {
// タスクがInprogressの場合はエラー
message = getTranslationID("dictationPage.message.deleteFailedError");
}
}
thunkApi.dispatch(
openSnackbar({
level: "error",
message,
})
);
return thunkApi.rejectWithValue({ error });
}
});
export const renameFileAsync = createAsyncThunk<
{
// empty
},
{
// パラメータ
audioFileId: number;
fileName: string;
},
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("dictations/renameFileAsync", async (args, thunkApi) => {
const { audioFileId, fileName } = args;
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration } = state.auth;
const accessToken = getAccessToken(state.auth);
const config = new Configuration(configuration);
const filesApi = new FilesApi(config);
try {
await filesApi.fileRename(
{ fileName, audioFileId },
{ headers: { authorization: `Bearer ${accessToken}` } }
);
thunkApi.dispatch(
openSnackbar({
level: "info",
message: getTranslationID("common.message.success"),
})
);
return {};
} catch (e) {
// e ⇒ errorObjectに変換"
const error = createErrorObject(e);
let message = getTranslationID("common.message.internalServerError");
// 変更権限がない場合はエラー
if (error.code === "E021001") {
message = getTranslationID("dictationPage.message.fileRenameFailedError");
}
// ファイル名が既に存在する場合はエラー
if (error.code === "E021002") {
message = getTranslationID(
"dictationPage.message.fileNameAleadyExistsError"
);
}
thunkApi.dispatch(
openSnackbar({
level: "error",
message,
})
);
return thunkApi.rejectWithValue({ error });
}
});

View File

@ -72,12 +72,6 @@ export const selectDirection = (state: RootState) =>
export const selectParamName = (state: RootState) =>
state.dictation.apps.paramName;
export const selectAuthorId = (state: RootState) =>
state.dictation.apps.authorId;
export const selectFilename = (state: RootState) =>
state.dictation.apps.fileName;
export const selectSelectedTask = (state: RootState) =>
state.dictation.apps.selectedTask;

View File

@ -25,8 +25,6 @@ export interface Apps {
displayInfo: DisplayInfoType;
direction: DirectionType;
paramName: SortableColumnType;
authorId: string;
fileName: string;
selectedTask?: Task;
selectedFileTask?: Task;
assignee: {

View File

@ -1,6 +1,5 @@
import { createSlice } from "@reduxjs/toolkit";
import { LicenseCardActivateState } from "./state";
import { activateCardLicenseAsync } from "./operations";
const initialState: LicenseCardActivateState = {
apps: {
@ -15,17 +14,6 @@ export const licenseCardActivateSlice = createSlice({
state.apps = initialState.apps;
},
},
extraReducers: (builder) => {
builder.addCase(activateCardLicenseAsync.pending, (state) => {
state.apps.isLoading = true;
});
builder.addCase(activateCardLicenseAsync.fulfilled, (state) => {
state.apps.isLoading = false;
});
builder.addCase(activateCardLicenseAsync.rejected, (state) => {
state.apps.isLoading = false;
});
},
});
export const { cleanupApps } = licenseCardActivateSlice.actions;

View File

@ -7,8 +7,3 @@ export const STATUS = {
// eslint-disable-next-line @typescript-eslint/naming-convention
ORDER_CANCELED: "Order Canceled",
} as const;
export const LICENSE_TYPE = {
NORMAL: "NORMAL",
TRIAL: "TRIAL",
} as const;

View File

@ -3,12 +3,7 @@ import type { RootState } from "app/store";
import { getTranslationID } from "translation";
import { openSnackbar } from "features/ui/uiSlice";
import { getAccessToken } from "features/auth";
import {
AccountsApi,
LicensesApi,
SearchPartner,
PartnerLicenseInfo,
} from "../../../api/api";
import { AccountsApi, LicensesApi } from "../../../api/api";
import { Configuration } from "../../../api/configuration";
import { ErrorObject, createErrorObject } from "../../../common/errors";
import { OrderHistoryView } from "./types";
@ -20,7 +15,6 @@ export const getLicenseOrderHistoriesAsync = createAsyncThunk<
// パラメータ
limit: number;
offset: number;
selectedRow?: PartnerLicenseInfo | SearchPartner;
},
{
// rejectした時の返却値の型
@ -29,7 +23,7 @@ export const getLicenseOrderHistoriesAsync = createAsyncThunk<
};
}
>("licenses/licenseOrderHisotyAsync", async (args, thunkApi) => {
const { limit, offset, selectedRow } = args;
const { limit, offset } = args;
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
@ -39,6 +33,7 @@ export const getLicenseOrderHistoriesAsync = createAsyncThunk<
const accountsApi = new AccountsApi(config);
try {
const { selectedRow } = state.partnerLicense.apps;
let accountId = 0;
let companyName = "";
// 他の画面から指定されていない場合はログインアカウントのidを取得する
@ -51,9 +46,7 @@ export const getLicenseOrderHistoriesAsync = createAsyncThunk<
companyName = getMyAccountResponse.data.account.companyName;
} else {
accountId = selectedRow.accountId;
// パートナーライセンスとパートナー検索で型が異なるため、型ガードで推論させる
if ("companyName" in selectedRow) companyName = selectedRow.companyName;
if ("name" in selectedRow) companyName = selectedRow.name;
companyName = selectedRow.companyName;
}
const res = await accountsApi.getOrderHistories(

View File

@ -1,10 +1,6 @@
import { createSlice } from "@reduxjs/toolkit";
import { LicenseSummaryState } from "./state";
import {
getCompanyNameAsync,
getLicenseSummaryAsync,
updateRestrictionStatusAsync,
} from "./operations";
import { getCompanyNameAsync, getLicenseSummaryAsync } from "./operations";
const initialState: LicenseSummaryState = {
domain: {
@ -39,30 +35,12 @@ export const licenseSummarySlice = createSlice({
},
},
extraReducers: (builder) => {
builder.addCase(getLicenseSummaryAsync.pending, (state) => {
state.apps.isLoading = true;
});
builder.addCase(getLicenseSummaryAsync.fulfilled, (state, action) => {
state.domain.licenseSummaryInfo = action.payload;
state.apps.isLoading = false;
});
builder.addCase(getLicenseSummaryAsync.rejected, (state) => {
state.apps.isLoading = false;
});
// 画面側ではgetLicenseSummaryAsyncと並行して呼び出されているため、レーシングを考慮してこちらではisLoadingを更新しない
// 本来は両方の完了を待ってからisLoadingを更新するべきだが、現時点ではスピード重視のためケアしない。
builder.addCase(getCompanyNameAsync.fulfilled, (state, action) => {
state.domain.accountInfo.companyName = action.payload.companyName;
});
builder.addCase(updateRestrictionStatusAsync.pending, (state) => {
state.apps.isLoading = true;
});
builder.addCase(updateRestrictionStatusAsync.fulfilled, (state) => {
state.apps.isLoading = false;
});
builder.addCase(updateRestrictionStatusAsync.rejected, (state) => {
state.apps.isLoading = false;
});
},
});

View File

@ -7,9 +7,7 @@ import {
AccountsApi,
GetCompanyNameResponse,
GetLicenseSummaryResponse,
SearchPartner,
PartnerLicenseInfo,
UpdateRestrictionStatusRequest,
} from "../../../api/api";
import { Configuration } from "../../../api/configuration";
import { ErrorObject, createErrorObject } from "../../../common/errors";
@ -18,7 +16,7 @@ export const getLicenseSummaryAsync = createAsyncThunk<
// 正常時の戻り値の型
GetLicenseSummaryResponse,
// 引数
{ selectedRow?: PartnerLicenseInfo | SearchPartner },
{ selectedRow?: PartnerLicenseInfo },
{
// rejectした時の返却値の型
rejectValue: {
@ -74,7 +72,7 @@ export const getCompanyNameAsync = createAsyncThunk<
// 正常時の戻り値の型
GetCompanyNameResponse,
// 引数
{ selectedRow?: PartnerLicenseInfo | SearchPartner },
{ selectedRow?: PartnerLicenseInfo },
{
// rejectした時の返却値の型
rejectValue: {
@ -125,58 +123,3 @@ export const getCompanyNameAsync = createAsyncThunk<
return thunkApi.rejectWithValue({ error });
}
});
export const updateRestrictionStatusAsync = createAsyncThunk<
{
/* Empty Object */
},
{
accountId: number;
restricted: boolean;
},
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("accounts/updateRestrictionStatusAsync", async (args, thunkApi) => {
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration } = state.auth;
const accessToken = getAccessToken(state.auth);
const config = new Configuration(configuration);
const accountApi = new AccountsApi(config);
const requestParam: UpdateRestrictionStatusRequest = {
accountId: args.accountId,
restricted: args.restricted,
};
try {
await accountApi.updateRestrictionStatus(requestParam, {
headers: { authorization: `Bearer ${accessToken}` },
});
thunkApi.dispatch(
openSnackbar({
level: "info",
message: getTranslationID("common.message.success"),
})
);
return {};
} catch (e) {
const error = createErrorObject(e);
// このAPIでは個別のエラーメッセージは不要
const errorMessage = getTranslationID("common.message.internalServerError");
thunkApi.dispatch(
openSnackbar({
level: "error",
message: errorMessage,
})
);
return thunkApi.rejectWithValue({ error });
}
});

View File

@ -1,11 +1,10 @@
import { RootState } from "app/store";
// 各値はそのまま画面に表示するので、licenseSummaryInfoとして値を取得する
export const selectLicenseSummaryInfo = (state: RootState) =>
export const selecLicenseSummaryInfo = (state: RootState) =>
state.licenseSummary.domain.licenseSummaryInfo;
export const selectCompanyName = (state: RootState) =>
state.licenseSummary.domain.accountInfo.companyName;
export const selectIsLoading = (state: RootState) =>
state.licenseSummary.apps.isLoading;
export const selectIsLoading = (state: RootState) => state.license;

View File

@ -1,2 +0,0 @@
export const ISSUED_TRIAL_LICENSE_QUANTITY = 10;
export const TRIAL_LICENSE_EXPIRATION_DAY = 30;

View File

@ -1,5 +0,0 @@
export * from "./state";
export * from "./operations";
export * from "./selectors";
export * from "./licenseTrialIssueSlice";
export * from "./constants";

View File

@ -1,60 +0,0 @@
import { createSlice } from "@reduxjs/toolkit";
import { convertLocalToUTCDate } from "common/convertLocalToUTCDate";
import { LicenseTrialIssueState } from "./state";
import { issueTrialLicenseAsync } from "./operations";
import {
TRIAL_LICENSE_EXPIRATION_DAY,
ISSUED_TRIAL_LICENSE_QUANTITY,
} from "./constants";
const initialState: LicenseTrialIssueState = {
apps: {
isLoading: false,
expirationDate: "",
quantity: ISSUED_TRIAL_LICENSE_QUANTITY,
},
};
export const licenseTrialIssueSlice = createSlice({
name: "licenseTrialIssue",
initialState,
reducers: {
cleanupApps: (state) => {
state.apps = initialState.apps;
},
setExpirationDate: (state) => {
// 有効期限を設定
const currentDate = new Date();
const expiryDate = new Date();
expiryDate.setDate(currentDate.getDate() + TRIAL_LICENSE_EXPIRATION_DAY);
// タイムゾーンオフセットを考慮して、ローカルタイムでの日付を取得
const expirationDateLocal = convertLocalToUTCDate(expiryDate);
const expirationDateWithoutTime = new Date(
expirationDateLocal.getFullYear(),
expirationDateLocal.getMonth(),
expirationDateLocal.getDate()
);
const expirationYear = expirationDateWithoutTime.getFullYear();
const expirationMonth = expirationDateWithoutTime.getMonth() + 1; // getMonth() の結果は0から始まるため、1を足して実際の月に合わせる
const expirationDay = expirationDateWithoutTime.getDate();
const formattedExpirationDate = `${expirationYear}/${expirationMonth}/${expirationDay} (${TRIAL_LICENSE_EXPIRATION_DAY})`;
state.apps.expirationDate = formattedExpirationDate;
},
},
extraReducers: (builder) => {
builder.addCase(issueTrialLicenseAsync.pending, (state) => {
state.apps.isLoading = true;
});
builder.addCase(issueTrialLicenseAsync.fulfilled, (state) => {
state.apps.isLoading = false;
});
builder.addCase(issueTrialLicenseAsync.rejected, (state) => {
state.apps.isLoading = false;
});
},
});
export const { cleanupApps, setExpirationDate } =
licenseTrialIssueSlice.actions;
export default licenseTrialIssueSlice.reducer;

View File

@ -1,84 +0,0 @@
import { createAsyncThunk } from "@reduxjs/toolkit";
import type { RootState } from "app/store";
import { getTranslationID } from "translation";
import { openSnackbar } from "features/ui/uiSlice";
import { getAccessToken } from "features/auth";
import {
LicensesApi,
SearchPartner,
PartnerLicenseInfo,
} from "../../../api/api";
import { Configuration } from "../../../api/configuration";
import { ErrorObject, createErrorObject } from "../../../common/errors";
export const issueTrialLicenseAsync = createAsyncThunk<
{
/* Empty Object */
},
{
selectedRow?: PartnerLicenseInfo | SearchPartner;
},
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("licenses/issueTrialLicenseAsync", async (args, thunkApi) => {
const { selectedRow } = args;
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration } = state.auth;
const accessToken = getAccessToken(state.auth);
const config = new Configuration(configuration);
const licensesApi = new LicensesApi(config);
try {
if (!selectedRow) {
// アカウントが選択されていない場合はエラーとする。
const errorMessage = getTranslationID(
"trialLicenseIssuePopupPage.message.accountNotSelected"
);
thunkApi.dispatch(
openSnackbar({
level: "error",
message: errorMessage,
})
);
return {};
}
// トライアルライセンス発行処理を実行
await licensesApi.issueTrialLicenses(
{
issuedAccount: selectedRow.accountId,
},
{
headers: { authorization: `Bearer ${accessToken}` },
}
);
thunkApi.dispatch(
openSnackbar({
level: "info",
message: getTranslationID("common.message.success"),
})
);
return {};
} catch (e) {
// e ⇒ errorObjectに変換"
const error = createErrorObject(e);
const errorMessage = getTranslationID("common.message.internalServerError");
thunkApi.dispatch(
openSnackbar({
level: "error",
message: errorMessage,
})
);
return thunkApi.rejectWithValue({ error });
}
});

View File

@ -1,10 +0,0 @@
import { RootState } from "app/store";
export const selectIsLoading = (state: RootState) =>
state.licenseTrialIssue.apps.isLoading;
export const selectExpirationDate = (state: RootState) =>
state.licenseTrialIssue.apps.expirationDate;
export const selectNumberOfLicenses = (state: RootState) =>
state.licenseTrialIssue.apps.quantity;

View File

@ -1,9 +0,0 @@
export interface LicenseTrialIssueState {
apps: Apps;
}
export interface Apps {
isLoading: boolean;
expirationDate: string;
quantity: number;
}

View File

@ -105,82 +105,3 @@ export const getPartnerLicenseAsync = createAsyncThunk<
return thunkApi.rejectWithValue({ error });
}
});
export const switchParentAsync = createAsyncThunk<
{
/* Empty Object */
},
{
// パラメータ
to: number;
children: number[];
},
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("accounts/switchParentAsync", async (args, thunkApi) => {
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration } = state.auth;
const accessToken = getAccessToken(state.auth);
const config = new Configuration(configuration);
const accountsApi = new AccountsApi(config);
const { to, children } = args;
try {
await accountsApi.switchParent(
{
to,
children,
},
{
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");
// TODO:エラー処理
if (error.code === "E017001") {
errorMessage = getTranslationID(
"changeOwnerPopup.message.accountNotFoundError"
);
}
if (error.code === "E017002") {
errorMessage = getTranslationID(
"changeOwnerPopup.message.hierarchyMismatchError"
);
}
if (error.code === "E017003") {
errorMessage = getTranslationID(
"changeOwnerPopup.message.regionMismatchError"
);
}
thunkApi.dispatch(
openSnackbar({
level: "error",
message: errorMessage,
})
);
return thunkApi.rejectWithValue({ error });
}
});

View File

@ -1,11 +1,7 @@
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
import { PartnerLicenseInfo } from "api";
import { PartnerLicensesState, HierarchicalElement } from "./state";
import {
getMyAccountAsync,
getPartnerLicenseAsync,
switchParentAsync,
} from "./operations";
import { getMyAccountAsync, getPartnerLicenseAsync } from "./operations";
import { ACCOUNTS_VIEW_LIMIT } from "./constants";
const initialState: PartnerLicensesState = {
@ -16,8 +12,6 @@ const initialState: PartnerLicensesState = {
tier: 0,
country: "",
delegationPermission: false,
autoFileDelete: false,
fileRetentionDays: 0,
},
total: 0,
ownPartnerLicense: {
@ -25,7 +19,6 @@ const initialState: PartnerLicensesState = {
tier: 0,
companyName: "",
stockLicense: 0,
allocatedLicense: 0,
issuedRequested: 0,
shortage: 0,
issueRequesting: 0,
@ -40,9 +33,6 @@ const initialState: PartnerLicensesState = {
hierarchicalElements: [],
isLoading: true,
selectedRow: undefined,
isLicenseOrderHistoryOpen: false,
isViewDetailsOpen: false,
isSearchPopupOpen: false,
},
};
@ -71,9 +61,6 @@ export const partnerLicenseSlice = createSlice({
const { deleteCount } = action.payload;
state.apps.hierarchicalElements.splice(-deleteCount);
},
clearHierarchicalElement: (state) => {
state.apps.hierarchicalElements = [];
},
changeSelectedRow: (
state,
action: PayloadAction<{ value?: PartnerLicenseInfo }>
@ -92,24 +79,6 @@ export const partnerLicenseSlice = createSlice({
state.apps.limit = limit;
state.apps.offset = offset;
},
setIsLicenseOrderHistoryOpen: (
state,
action: PayloadAction<{ value: boolean }>
) => {
state.apps.isLicenseOrderHistoryOpen = action.payload.value;
},
setIsViewDetailsOpen: (
state,
action: PayloadAction<{ value: boolean }>
) => {
state.apps.isViewDetailsOpen = action.payload.value;
},
setIsSearchPopupOpen: (
state,
action: PayloadAction<{ value: boolean }>
) => {
state.apps.isSearchPopupOpen = action.payload.value;
},
},
extraReducers: (builder) => {
builder.addCase(getMyAccountAsync.pending, (state) => {
@ -135,27 +104,14 @@ export const partnerLicenseSlice = createSlice({
builder.addCase(getPartnerLicenseAsync.rejected, (state) => {
state.apps.isLoading = false;
});
builder.addCase(switchParentAsync.pending, (state) => {
state.apps.isLoading = true;
});
builder.addCase(switchParentAsync.fulfilled, (state) => {
state.apps.isLoading = false;
});
builder.addCase(switchParentAsync.rejected, (state) => {
state.apps.isLoading = false;
});
},
});
export const {
pushHierarchicalElement,
popHierarchicalElement,
spliceHierarchicalElement,
clearHierarchicalElement,
changeSelectedRow,
savePageInfo,
setIsLicenseOrderHistoryOpen,
setIsViewDetailsOpen,
setIsSearchPopupOpen,
} = partnerLicenseSlice.actions;
export default partnerLicenseSlice.reducer;

View File

@ -30,10 +30,3 @@ export const selectCurrentPage = (state: RootState) => {
};
export const selectSelectedRow = (state: RootState) =>
state.partnerLicense.apps.selectedRow;
export const selectIsLicenseOrderHistoryOpen = (state: RootState) =>
state.partnerLicense.apps.isLicenseOrderHistoryOpen;
export const selectIsViewDetailsOpen = (state: RootState) =>
state.partnerLicense.apps.isViewDetailsOpen;
export const selectIsSearchPopupOpen = (state: RootState) =>
state.partnerLicense.apps.isSearchPopupOpen;

View File

@ -20,9 +20,6 @@ export interface Apps {
hierarchicalElements: HierarchicalElement[];
isLoading: boolean;
selectedRow?: PartnerLicenseInfo;
isLicenseOrderHistoryOpen: boolean;
isViewDetailsOpen: boolean;
isSearchPopupOpen: boolean;
}
export interface HierarchicalElement {

View File

@ -1,4 +0,0 @@
export * from "./state";
export * from "./operations";
export * from "./selectors";
export * from "./searchPartnerSlice";

View File

@ -1,96 +0,0 @@
import { createAsyncThunk } from "@reduxjs/toolkit";
import { getAccessToken } from "features/auth";
import type { RootState } from "../../../app/store";
import { getTranslationID } from "../../../translation";
import { openSnackbar } from "../../ui/uiSlice";
import { AccountsApi, SearchPartner, PartnerHierarchy } from "../../../api/api";
import { Configuration } from "../../../api/configuration";
import { ErrorObject, createErrorObject } from "../../../common/errors";
export const searchPartnersAsync = createAsyncThunk<
// 正常時の戻り値の型
SearchPartner[],
// 引数
{
companyName?: string;
accountId?: number;
},
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("licenses/searchPartners", async (args, thunkApi) => {
// apiのConfigurationを取得する
const { companyName, accountId } = args;
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration } = state.auth;
const accessToken = getAccessToken(state.auth);
const config = new Configuration(configuration);
const accountsApi = new AccountsApi(config);
try {
const searchPartnerResponse = await accountsApi.searchPartners(
companyName,
accountId,
{
headers: { authorization: `Bearer ${accessToken}` },
}
);
return searchPartnerResponse.data.searchResult;
} catch (e) {
// e ⇒ errorObjectに変換"
const error = createErrorObject(e);
thunkApi.dispatch(
openSnackbar({
level: "error",
message: getTranslationID("common.message.internalServerError"),
})
);
return thunkApi.rejectWithValue({ error });
}
});
export const getPartnerHierarchy = createAsyncThunk<
// 正常時の戻り値の型
PartnerHierarchy[],
// 引数
{
accountId: number;
},
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("licenses/getPartnerHierarchy", async (args, thunkApi) => {
// apiのConfigurationを取得する
const { accountId } = args;
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration } = state.auth;
const accessToken = getAccessToken(state.auth);
const config = new Configuration(configuration);
const accountsApi = new AccountsApi(config);
try {
const partnerHierarchyResponse = await accountsApi.getPartnerHierarchy(
accountId,
{
headers: { authorization: `Bearer ${accessToken}` },
}
);
return partnerHierarchyResponse.data.accountHierarchy;
} catch (e) {
// e ⇒ errorObjectに変換"
const error = createErrorObject(e);
thunkApi.dispatch(
openSnackbar({
level: "error",
message: getTranslationID("common.message.internalServerError"),
})
);
return thunkApi.rejectWithValue({ error });
}
});

View File

@ -1,80 +0,0 @@
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
import { SearchPartner } from "../../../api";
import { SearchPartnerState } from "./state";
import { searchPartnersAsync, getPartnerHierarchy } from "./operations";
const initialState: SearchPartnerState = {
domain: {
searchResult: [],
partnerHierarchy: [],
},
apps: {
isLoading: false,
selectedRow: undefined,
isLicenseOrderHistoryOpen: false,
isViewDetailsOpen: false,
},
};
export const searchPartnersSlice = createSlice({
name: "searchPartners",
initialState,
reducers: {
changeSelectedRow: (
state,
action: PayloadAction<{ value?: SearchPartner }>
) => {
const { value } = action.payload;
state.apps.selectedRow = value;
},
setIsLicenseOrderHistoryOpen: (
state,
action: PayloadAction<{ value: boolean }>
) => {
state.apps.isLicenseOrderHistoryOpen = action.payload.value;
},
setIsViewDetailsOpen: (
state,
action: PayloadAction<{ value: boolean }>
) => {
state.apps.isViewDetailsOpen = action.payload.value;
},
cleanupSearchResult: (state) => {
state.domain.searchResult = initialState.domain.searchResult;
},
cleanupPartnerHierarchy: (state) => {
state.domain.partnerHierarchy = initialState.domain.partnerHierarchy;
},
},
extraReducers: (builder) => {
builder.addCase(searchPartnersAsync.pending, (state) => {
state.apps.isLoading = true;
});
builder.addCase(searchPartnersAsync.fulfilled, (state, action) => {
state.domain.searchResult = action.payload;
state.apps.isLoading = false;
});
builder.addCase(searchPartnersAsync.rejected, (state) => {
state.apps.isLoading = false;
});
builder.addCase(getPartnerHierarchy.pending, (state) => {
state.apps.isLoading = true;
});
builder.addCase(getPartnerHierarchy.fulfilled, (state, action) => {
state.domain.partnerHierarchy = action.payload;
state.apps.isLoading = false;
});
builder.addCase(getPartnerHierarchy.rejected, (state) => {
state.apps.isLoading = false;
});
},
});
export const {
changeSelectedRow,
setIsLicenseOrderHistoryOpen,
setIsViewDetailsOpen,
cleanupSearchResult,
cleanupPartnerHierarchy,
} = searchPartnersSlice.actions;
export default searchPartnersSlice.reducer;

View File

@ -1,14 +0,0 @@
import { RootState } from "../../../app/store";
export const selectSearchResult = (state: RootState) =>
state.searchPartners.domain.searchResult;
export const selectPartnerHierarchy = (state: RootState) =>
state.searchPartners.domain.partnerHierarchy;
export const selectIsLoading = (state: RootState) =>
state.searchPartners.apps.isLoading;
export const selectSelectedRow = (state: RootState) =>
state.searchPartners.apps.selectedRow;
export const selectIsLicenseOrderHistoryOpen = (state: RootState) =>
state.searchPartners.apps.isLicenseOrderHistoryOpen;
export const selectIsViewDetailsOpen = (state: RootState) =>
state.searchPartners.apps.isViewDetailsOpen;

View File

@ -1,18 +0,0 @@
import { SearchPartner, PartnerHierarchy } from "../../../api/api";
export interface SearchPartnerState {
domain: Domain;
apps: Apps;
}
export interface Domain {
searchResult: SearchPartner[];
partnerHierarchy: PartnerHierarchy[];
}
export interface Apps {
isLoading: boolean;
selectedRow?: SearchPartner;
isLicenseOrderHistoryOpen: boolean;
isViewDetailsOpen: boolean;
}

View File

@ -8,8 +8,6 @@ import {
AccountsApi,
CreatePartnerAccountRequest,
GetPartnersResponse,
DeletePartnerAccountRequest,
GetPartnerUsersResponse,
} from "../../api/api";
import { Configuration } from "../../api/configuration";
@ -118,170 +116,3 @@ export const getPartnerInfoAsync = createAsyncThunk<
return thunkApi.rejectWithValue({ error });
}
});
// パートナーアカウント削除
export const deletePartnerAccountAsync = createAsyncThunk<
{
/* Empty Object */
},
{
// パラメータ
accountId: number;
},
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("partner/deletePartnerAccountAsync", async (args, thunkApi) => {
const { accountId } = args;
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration } = state.auth;
const accessToken = getAccessToken(state.auth);
const config = new Configuration(configuration);
const accountApi = new AccountsApi(config);
try {
const deletePartnerAccountRequest: DeletePartnerAccountRequest = {
targetAccountId: accountId,
};
await accountApi.deletePartnerAccount(deletePartnerAccountRequest, {
headers: { authorization: `Bearer ${accessToken}` },
});
thunkApi.dispatch(
openSnackbar({
level: "info",
message: getTranslationID("common.message.success"),
})
);
return {};
} catch (e) {
const error = createErrorObject(e);
let errorMessage = getTranslationID("common.message.internalServerError");
if (error.code === "E018001") {
errorMessage = getTranslationID(
"partnerPage.message.partnerDeleteFailedError"
);
}
thunkApi.dispatch(
openSnackbar({
level: "error",
message: errorMessage,
})
);
return thunkApi.rejectWithValue({ error });
}
});
// パートナーアカウントユーザー取得
export const getPartnerUsersAsync = createAsyncThunk<
GetPartnerUsersResponse,
{
// パラメータ
accountId: number;
},
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("partner/getPartnerUsersAsync", async (args, thunkApi) => {
const { accountId } = args;
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration } = state.auth;
const accessToken = getAccessToken(state.auth);
const config = new Configuration(configuration);
const accountApi = new AccountsApi(config);
try {
const res = await accountApi.getPartnerUsers(
{ targetAccountId: accountId },
{
headers: { authorization: `Bearer ${accessToken}` },
}
);
return res.data;
} catch (e) {
const error = createErrorObject(e);
thunkApi.dispatch(
openSnackbar({
level: "error",
message: getTranslationID("common.message.internalServerError"),
})
);
return thunkApi.rejectWithValue({ error });
}
});
// パートナーアカウントユーザー編集
export const editPartnerInfoAsync = createAsyncThunk<
{
/* Empty Object */
},
void,
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("partner/editPartnerInfoAsync", async (args, thunkApi) => {
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration } = state.auth;
const accessToken = getAccessToken(state.auth);
const config = new Configuration(configuration);
const accountApi = new AccountsApi(config);
const { id, companyName, selectedAdminId } = state.partner.apps.editPartner;
try {
await accountApi.updatePartnerInfo(
{
targetAccountId: id,
primaryAdminUserId: selectedAdminId,
companyName,
},
{
headers: { authorization: `Bearer ${accessToken}` },
}
);
thunkApi.dispatch(
openSnackbar({
level: "info",
message: getTranslationID("common.message.success"),
})
);
return {};
} catch (e) {
const error = createErrorObject(e);
let errorMessage = getTranslationID("common.message.internalServerError");
if (error.code === "E010502" || error.code === "E020001") {
errorMessage = getTranslationID("partnerPage.message.editFailedError");
}
thunkApi.dispatch(
openSnackbar({
level: "error",
message: errorMessage,
})
);
return thunkApi.rejectWithValue({ error });
}
});

View File

@ -1,12 +1,6 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { PartnerState } from "./state";
import {
createPartnerAccountAsync,
getPartnerInfoAsync,
deletePartnerAccountAsync,
getPartnerUsersAsync,
editPartnerInfoAsync,
} from "./operations";
import { createPartnerAccountAsync, getPartnerInfoAsync } from "./operations";
import { LIMIT_PARTNER_VIEW_NUM } from "./constants";
const initialState: PartnerState = {
@ -23,13 +17,6 @@ const initialState: PartnerState = {
adminName: "",
email: "",
},
editPartner: {
users: [],
id: 0,
companyName: "",
country: "",
selectedAdminId: 0,
},
limit: LIMIT_PARTNER_VIEW_NUM,
offset: 0,
isLoading: false,
@ -88,37 +75,6 @@ export const partnerSlice = createSlice({
state.apps.delegatedAccountId = undefined;
state.apps.delegatedCompanyName = undefined;
},
changeEditPartner: (
state,
action: PayloadAction<{
id: number;
companyName: string;
country: string;
}>
) => {
const { id, companyName, country } = action.payload;
state.apps.editPartner.id = id;
state.apps.editPartner.companyName = companyName;
state.apps.editPartner.country = country;
},
changeEditCompanyName: (
state,
action: PayloadAction<{ companyName: string }>
) => {
const { companyName } = action.payload;
state.apps.editPartner.companyName = companyName;
},
changeSelectedAdminId: (
state,
action: PayloadAction<{ adminId: number }>
) => {
const { adminId } = action.payload;
state.apps.editPartner.selectedAdminId = adminId;
},
cleanupPartnerAccount: (state) => {
state.apps.editPartner = initialState.apps.editPartner;
},
},
extraReducers: (builder) => {
builder.addCase(createPartnerAccountAsync.pending, (state) => {
@ -141,37 +97,6 @@ export const partnerSlice = createSlice({
builder.addCase(getPartnerInfoAsync.rejected, (state) => {
state.apps.isLoading = false;
});
builder.addCase(deletePartnerAccountAsync.pending, (state) => {
state.apps.isLoading = true;
});
builder.addCase(deletePartnerAccountAsync.fulfilled, (state) => {
state.apps.isLoading = false;
});
builder.addCase(deletePartnerAccountAsync.rejected, (state) => {
state.apps.isLoading = false;
});
builder.addCase(getPartnerUsersAsync.pending, (state) => {
state.apps.isLoading = true;
});
builder.addCase(getPartnerUsersAsync.fulfilled, (state, action) => {
const { users } = action.payload;
state.apps.editPartner.users = users;
state.apps.editPartner.selectedAdminId =
users.find((user) => user.isPrimaryAdmin)?.id ?? 0;
state.apps.isLoading = false;
});
builder.addCase(getPartnerUsersAsync.rejected, (state) => {
state.apps.isLoading = false;
});
builder.addCase(editPartnerInfoAsync.pending, (state) => {
state.apps.isLoading = true;
});
builder.addCase(editPartnerInfoAsync.fulfilled, (state) => {
state.apps.isLoading = false;
});
builder.addCase(editPartnerInfoAsync.rejected, (state) => {
state.apps.isLoading = false;
});
},
});
export const {
@ -183,9 +108,5 @@ export const {
savePageInfo,
changeDelegateAccount,
cleanupDelegateAccount,
changeEditPartner,
changeEditCompanyName,
changeSelectedAdminId,
cleanupPartnerAccount,
} = partnerSlice.actions;
export default partnerSlice.reducer;

View File

@ -62,17 +62,3 @@ export const selectDelegatedAccountId = (state: RootState) =>
state.partner.apps.delegatedAccountId;
export const selectDelegatedCompanyName = (state: RootState) =>
state.partner.apps.delegatedCompanyName;
// edit
export const selectEditPartnerId = (state: RootState) =>
state.partner.apps.editPartner.id;
export const selectEditPartnerCompanyName = (state: RootState) =>
state.partner.apps.editPartner.companyName;
export const selectEditPartnerCountry = (state: RootState) =>
state.partner.apps.editPartner.country;
export const selectEditPartnerUsers = (state: RootState) =>
state.partner.apps.editPartner.users;
export const selectSelectedAdminId = (state: RootState) =>
state.partner.apps.editPartner.selectedAdminId;

View File

@ -1,7 +1,6 @@
import {
CreatePartnerAccountRequest,
GetPartnersResponse,
PartnerUser,
} from "../../api/api";
export interface PartnerState {
@ -20,11 +19,4 @@ export interface Apps {
isLoading: boolean;
delegatedAccountId?: number;
delegatedCompanyName?: string;
editPartner: {
users: PartnerUser[];
id: number;
companyName: string;
country: string;
selectedAdminId: number;
};
}

View File

@ -9,7 +9,6 @@ import {
UsersApi,
LicensesApi,
GetAllocatableLicensesResponse,
MultipleImportUser,
} from "../../api/api";
import { Configuration } from "../../api/configuration";
import { ErrorObject, createErrorObject } from "../../common/errors";
@ -18,7 +17,7 @@ export const listUsersAsync = createAsyncThunk<
// 正常時の戻り値の型
GetUsersResponse,
// 引数
undefined | { userInputUserName?: string; userInputEmail?: string },
void,
{
// rejectした時の返却値の型
rejectValue: {
@ -33,11 +32,9 @@ export const listUsersAsync = createAsyncThunk<
const accessToken = getAccessToken(state.auth);
const config = new Configuration(configuration);
const usersApi = new UsersApi(config);
const userInputUserName = args?.userInputUserName;
const userInputEmail = args?.userInputEmail;
try {
const res = await usersApi.getUsers(userInputUserName, userInputEmail, {
const res = await usersApi.getUsers({
headers: { authorization: `Bearer ${accessToken}` },
});
@ -386,255 +383,3 @@ export const deallocateLicenseAsync = createAsyncThunk<
return thunkApi.rejectWithValue({ error });
}
});
export const deleteUserAsync = createAsyncThunk<
// 正常時の戻り値の型
{
/* Empty Object */
},
// 引数
{
userId: number;
},
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("users/deleteUserAsync", async (args, thunkApi) => {
const { userId } = args;
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration } = state.auth;
const accessToken = getAccessToken(state.auth);
const config = new Configuration(configuration);
const usersApi = new UsersApi(config);
try {
await usersApi.deleteUser(
{
userId,
},
{
headers: { authorization: `Bearer ${accessToken}` },
}
);
thunkApi.dispatch(
openSnackbar({
level: "info",
message: getTranslationID("common.message.success"),
})
);
return {};
} catch (e) {
// e ⇒ errorObjectに変換
const error = createErrorObject(e);
let errorMessage = getTranslationID("common.message.internalServerError");
if (error.statusCode === 400) {
if (error.code === "E014001") {
// ユーザーが削除済みのため成功
thunkApi.dispatch(
openSnackbar({
level: "info",
message: getTranslationID("common.message.success"),
})
);
return {};
}
}
// ユーザーに有効なライセンスが割り当たっているため削除不可
if (error.code === "E014007") {
errorMessage = getTranslationID(
"userListPage.message.userDeletionLicenseActiveError"
);
}
// 管理者ユーザーため削除不可
if (error.code === "E014002") {
errorMessage = getTranslationID(
"userListPage.message.adminUserDeletionError"
);
}
// タイピストユーザーで担当タスクがあるため削除不可
if (error.code === "E014009") {
errorMessage = getTranslationID(
"userListPage.message.typistUserDeletionTranscriptionTaskError"
);
}
// タイピストユーザーでルーティングルールに設定されているため削除不可
if (error.code === "E014004") {
errorMessage = getTranslationID(
"userListPage.message.typistDeletionRoutingRuleError"
);
}
// タイピストユーザーでTranscriptionistGroupに所属しているため削除不可
if (error.code === "E014005") {
errorMessage = getTranslationID(
"userListPage.message.typistUserDeletionTranscriptionistGroupError"
);
}
// Authorユーザーで同一AuthorIDのタスクがあるため削除不可
if (error.code === "E014006") {
errorMessage = getTranslationID(
"userListPage.message.authorUserDeletionTranscriptionTaskError"
);
}
// Authorユーザーで同一AuthorIDがルーティングルールに設定されているため削除不可
if (error.code === "E014003") {
errorMessage = getTranslationID(
"userListPage.message.authorDeletionRoutingRuleError"
);
}
thunkApi.dispatch(
openSnackbar({
level: "error",
message: errorMessage,
})
);
return thunkApi.rejectWithValue({ error });
}
});
export const confirmUserForceAsync = createAsyncThunk<
// 正常時の戻り値の型
{
/* Empty Object */
},
// 引数
{
userId: number;
},
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("users/confirmUserForceAsync", async (args, thunkApi) => {
const { userId } = args;
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration } = state.auth;
const accessToken = getAccessToken(state.auth);
const config = new Configuration(configuration);
const usersApi = new UsersApi(config);
try {
await usersApi.confirmUserForce(
{
userId,
},
{
headers: { authorization: `Bearer ${accessToken}` },
}
);
thunkApi.dispatch(
openSnackbar({
level: "info",
message: getTranslationID("common.message.success"),
})
);
return {};
} catch (e) {
// e ⇒ errorObjectに変換
const error = createErrorObject(e);
let errorMessage = getTranslationID("common.message.internalServerError");
// ユーザーが既に認証済みのため、強制認証不可
if (error.code === "E010202") {
errorMessage = getTranslationID(
"userListPage.message.alreadyEmailVerifiedError"
);
}
thunkApi.dispatch(
openSnackbar({
level: "error",
message: errorMessage,
})
);
return thunkApi.rejectWithValue({ error });
}
});
export const importUsersAsync = createAsyncThunk<
// 正常時の戻り値の型
{
/* Empty Object */
},
// 引数
void,
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("users/importUsersAsync", async (args, thunkApi) => {
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration } = state.auth;
const { importFileName, importUsers } = state.user.apps;
const accessToken = getAccessToken(state.auth);
const config = new Configuration(configuration);
const usersApi = new UsersApi(config);
try {
if (importFileName === undefined) {
throw new Error("importFileName is undefined");
}
// CSVデータをAPIに送信するためのデータに変換
const users: MultipleImportUser[] = importUsers.map((user) => ({
name: user.name ?? "",
email: user.email ?? "",
role: user.role ?? 0,
authorId: user.author_id ?? undefined,
autoRenew: user.auto_assign ?? 0,
notification: user.notification ?? 0,
encryption: user.encryption ?? undefined,
encryptionPassword: user.encryption_password ?? undefined,
prompt: user.prompt ?? undefined,
}));
await usersApi.multipleImports(
{
filename: importFileName,
users,
},
{ headers: { authorization: `Bearer ${accessToken}` } }
);
thunkApi.dispatch(
openSnackbar({
level: "info",
message: getTranslationID("userListPage.message.importSuccess"),
})
);
return {};
} catch (e) {
// e ⇒ errorObjectに変換
const error = createErrorObject(e);
thunkApi.dispatch(
openSnackbar({
level: "error",
message: getTranslationID("common.message.internalServerError"),
})
);
return thunkApi.rejectWithValue({ error });
}
});

View File

@ -382,142 +382,3 @@ const convertValueBasedOnLicenseStatus = (
remaining: undefined,
};
};
export const selectImportFileName = (state: RootState) =>
state.user.apps.importFileName;
export const selectImportValidationErrors = (state: RootState) => {
const csvUsers = state.user.apps.importUsers;
let rowNumber = 1;
const invalidInput: number[] = [];
const duplicatedEmailsMap = new Map<string, number>();
const duplicatedAuthorIdsMap = new Map<string, number>();
const overMaxRow = csvUsers.length > 100;
// eslint-disable-next-line no-restricted-syntax
for (const csvUser of csvUsers) {
rowNumber += 1;
// メールアドレスの重複がある場合、エラーとしてその行番号を追加する
const duplicatedEmailUser = csvUsers.filter(
(x) => x.email === csvUser.email
);
if (duplicatedEmailUser.length > 1) {
if (csvUser.email !== null && !duplicatedEmailsMap.has(csvUser.email)) {
duplicatedEmailsMap.set(csvUser.email, rowNumber);
}
}
// AuthorIDの重複がある場合、エラーとしてその行番号を追加する
const duplicatedAuthorIdUser = csvUsers.filter(
(x) => x.author_id === csvUser.author_id
);
if (duplicatedAuthorIdUser.length > 1) {
if (
csvUser.author_id !== null &&
!duplicatedAuthorIdsMap.has(csvUser.author_id)
) {
duplicatedAuthorIdsMap.set(csvUser.author_id, rowNumber);
}
}
// name
if (csvUser.name === null || csvUser.name.length > 225) {
invalidInput.push(rowNumber);
// eslint-disable-next-line no-continue
continue;
}
// email
const emailPattern =
/^[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]+@[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]*\.[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]*[a-zA-Z]$/;
if (
csvUser.name === null ||
csvUser.name.length > 225 ||
!emailPattern.test(csvUser.email ?? "")
) {
invalidInput.push(rowNumber);
// eslint-disable-next-line no-continue
continue;
}
// role
if (csvUser.role === null || ![0, 1, 2].includes(csvUser.role)) {
invalidInput.push(rowNumber);
// eslint-disable-next-line no-continue
continue;
}
// role=1(Author)
if (csvUser.role === 1) {
// author_id
if (csvUser.author_id === null || csvUser.author_id.length > 16) {
invalidInput.push(rowNumber);
// eslint-disable-next-line no-continue
continue;
}
// 半角英数字と_の組み合わせで文字まで
const charaTypePattern = /^[A-Z0-9_]{1,16}$/;
const charaType = new RegExp(charaTypePattern).test(csvUser.author_id);
if (!charaType) {
invalidInput.push(rowNumber);
// eslint-disable-next-line no-continue
continue;
}
// encryption
if (csvUser.encryption === null || ![0, 1].includes(csvUser.encryption)) {
invalidInput.push(rowNumber);
// eslint-disable-next-line no-continue
continue;
}
if (csvUser.encryption === 1) {
// encryption_password
if (csvUser.encryption === 1) {
const regex = /^[!-~]{4,16}$/;
if (!regex.test(csvUser.encryption_password ?? "")) {
invalidInput.push(rowNumber);
// eslint-disable-next-line no-continue
continue;
}
}
}
// prompt
if (csvUser.prompt === null || ![0, 1].includes(csvUser.prompt)) {
invalidInput.push(rowNumber);
// eslint-disable-next-line no-continue
continue;
}
}
// auto_assign
if (csvUser.auto_assign === null || ![0, 1].includes(csvUser.auto_assign)) {
invalidInput.push(rowNumber);
// eslint-disable-next-line no-continue
continue;
}
// notification
if (
csvUser.notification === null ||
![0, 1].includes(csvUser.notification)
) {
invalidInput.push(rowNumber);
// eslint-disable-next-line no-continue
continue;
}
}
const duplicatedEmails = Array.from(duplicatedEmailsMap.values());
const duplicatedAuthorIds = Array.from(duplicatedAuthorIdsMap.values());
return {
invalidInput,
duplicatedEmails,
duplicatedAuthorIds,
overMaxRow,
};
};

View File

@ -1,4 +1,3 @@
import { CSVType } from "common/parser";
import { User, AllocatableLicenseInfo } from "../../api/api";
import { AddUser, UpdateUser, LicenseAllocateUser } from "./types";
@ -20,6 +19,4 @@ export interface Apps {
selectedlicenseId: number;
hasPasswordMask: boolean;
isLoading: boolean;
importFileName: string | undefined;
importUsers: CSVType[];
}

View File

@ -54,14 +54,14 @@ export interface LicenseAllocateUser {
remaining?: number;
}
export type RoleType = (typeof USER_ROLES)[keyof typeof USER_ROLES];
export type RoleType = typeof USER_ROLES[keyof typeof USER_ROLES];
// 受け取った値がUSER_ROLESの型であるかどうかを判定する
export const isRoleType = (role: string): role is RoleType =>
Object.values(USER_ROLES).includes(role as RoleType);
export type LicenseStatusType =
(typeof LICENSE_STATUS)[keyof typeof LICENSE_STATUS];
typeof LICENSE_STATUS[keyof typeof LICENSE_STATUS];
// 受け取った値がLicenseStatusTypeの型であるかどうかを判定する
export const isLicenseStatusType = (

View File

@ -1,6 +1,5 @@
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
import { USER_ROLES } from "components/auth/constants";
import { CSVType } from "common/parser";
import { UsersState } from "./state";
import {
addUserAsync,
@ -8,8 +7,6 @@ import {
updateUserAsync,
getAllocatableLicensesAsync,
deallocateLicenseAsync,
deleteUserAsync,
importUsersAsync,
} from "./operations";
import { RoleType, UserView } from "./types";
@ -63,8 +60,6 @@ const initialState: UsersState = {
selectedlicenseId: 0,
hasPasswordMask: false,
isLoading: false,
importFileName: undefined,
importUsers: [],
},
};
@ -246,21 +241,6 @@ export const userSlice = createSlice({
state.apps.licenseAllocateUser = initialState.apps.licenseAllocateUser;
state.apps.selectedlicenseId = initialState.apps.selectedlicenseId;
},
changeImportFileName: (
state,
action: PayloadAction<{ fileName: string }>
) => {
const { fileName } = action.payload;
state.apps.importFileName = fileName;
},
changeImportCsv: (state, action: PayloadAction<{ users: CSVType[] }>) => {
const { users } = action.payload;
state.apps.importUsers = users;
},
cleanupImportUsers: (state) => {
state.apps.importFileName = initialState.apps.importFileName;
state.apps.importUsers = initialState.apps.importUsers;
},
},
extraReducers: (builder) => {
builder.addCase(listUsersAsync.pending, (state) => {
@ -310,24 +290,6 @@ export const userSlice = createSlice({
builder.addCase(deallocateLicenseAsync.rejected, (state) => {
state.apps.isLoading = false;
});
builder.addCase(deleteUserAsync.pending, (state) => {
state.apps.isLoading = true;
});
builder.addCase(deleteUserAsync.fulfilled, (state) => {
state.apps.isLoading = false;
});
builder.addCase(deleteUserAsync.rejected, (state) => {
state.apps.isLoading = false;
});
builder.addCase(importUsersAsync.pending, (state) => {
state.apps.isLoading = true;
});
builder.addCase(importUsersAsync.fulfilled, (state) => {
state.apps.isLoading = false;
});
builder.addCase(importUsersAsync.rejected, (state) => {
state.apps.isLoading = false;
});
},
});
@ -355,9 +317,6 @@ export const {
changeLicenseAllocateUser,
changeSelectedlicenseId,
cleanupLicenseAllocateInfo,
changeImportFileName,
changeImportCsv,
cleanupImportUsers,
} = userSlice.actions;
export default userSlice.reducer;

View File

@ -115,78 +115,3 @@ export const uploadTemplateAsync = createAsyncThunk<
return thunkApi.rejectWithValue({ error });
}
});
export const deleteTemplateAsync = createAsyncThunk<
{
/* Empty Object */
},
{ templateFileId: number },
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("workflow/deleteTemplateAsync", async (args, thunkApi) => {
const { templateFileId } = args;
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration } = state.auth;
const accessToken = getAccessToken(state.auth);
const config = new Configuration(configuration);
const templateApi = new TemplatesApi(config);
try {
// ファイルを削除する
await templateApi.deleteTemplateFile(templateFileId, {
headers: { authorization: `Bearer ${accessToken}` },
});
thunkApi.dispatch(
openSnackbar({
level: "info",
message: getTranslationID("common.message.success"),
})
);
return {};
} catch (e) {
// e ⇒ errorObjectに変換"
const error = createErrorObject(e);
if (error.code === "E016001") {
// テンプレートファイルが削除済みの場合は成功扱いとする
thunkApi.dispatch(
openSnackbar({
level: "info",
message: getTranslationID("common.message.success"),
})
);
return {};
}
let message = getTranslationID("common.message.internalServerError");
// テンプレートファイルがルーティングルールに紐づく場合はエラー
if (error.code === "E016002") {
message = getTranslationID(
"templateFilePage.message.deleteFailedWorkflowAssigned"
);
}
// テンプレートファイルが未完了のタスクに紐づく場合はエラー
if (error.code === "E016003") {
message = getTranslationID(
"templateFilePage.message.deleteFailedTaskAssigned"
);
}
thunkApi.dispatch(
openSnackbar({
level: "error",
message,
})
);
return thunkApi.rejectWithValue({ error });
}
});

View File

@ -1,10 +1,6 @@
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
import { TemplateState } from "./state";
import {
deleteTemplateAsync,
listTemplateAsync,
uploadTemplateAsync,
} from "./operations";
import { listTemplateAsync, uploadTemplateAsync } from "./operations";
const initialState: TemplateState = {
apps: {
@ -49,15 +45,6 @@ export const templateSlice = createSlice({
builder.addCase(uploadTemplateAsync.rejected, (state) => {
state.apps.isUploading = false;
});
builder.addCase(deleteTemplateAsync.pending, (state) => {
state.apps.isLoading = true;
});
builder.addCase(deleteTemplateAsync.fulfilled, (state) => {
state.apps.isLoading = false;
});
builder.addCase(deleteTemplateAsync.rejected, (state) => {
state.apps.isLoading = false;
});
},
});

View File

@ -269,70 +269,3 @@ export const updateTypistGroupAsync = createAsyncThunk<
return thunkApi.rejectWithValue({ error });
}
});
export const deleteTypistGroupAsync = createAsyncThunk<
{
/* Empty Object */
},
{
typistGroupId: number;
},
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("workflow/deleteTypistGroupAsync", async (args, thunkApi) => {
const { typistGroupId } = args;
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration } = state.auth;
const accessToken = getAccessToken(state.auth);
const config = new Configuration(configuration);
const accountsApi = new AccountsApi(config);
try {
await accountsApi.deleteTypistGroup(typistGroupId, {
headers: { authorization: `Bearer ${accessToken}` },
});
thunkApi.dispatch(
openSnackbar({
level: "info",
message: getTranslationID("common.message.success"),
})
);
return {};
} catch (e) {
// e ⇒ errorObjectに変換"
const error = createErrorObject(e);
// すでに削除されていた場合は成功扱いする
if (error.code === "E015001") {
thunkApi.dispatch(
openSnackbar({
level: "info",
message: getTranslationID("common.message.success"),
})
);
return {};
}
// 以下は実際の削除失敗
let message = getTranslationID("common.message.internalServerError");
if (error.code === "E015002")
message = getTranslationID(
"typistGroupSetting.message.deleteFailedWorkflowAssigned"
);
if (error.code === "E015003")
message = getTranslationID(
"typistGroupSetting.message.deleteFailedCheckoutPermissionExisted"
);
thunkApi.dispatch(openSnackbar({ level: "error", message }));
return thunkApi.rejectWithValue({ error });
}
});

View File

@ -6,7 +6,6 @@ import {
listTypistGroupsAsync,
listTypistsAsync,
updateTypistGroupAsync,
deleteTypistGroupAsync,
} from "./operations";
const initialState: TypistGroupState = {
@ -107,15 +106,6 @@ export const typistGroupSlice = createSlice({
builder.addCase(updateTypistGroupAsync.rejected, (state) => {
state.apps.isLoading = false;
});
builder.addCase(deleteTypistGroupAsync.pending, (state) => {
state.apps.isLoading = true;
});
builder.addCase(deleteTypistGroupAsync.fulfilled, (state) => {
state.apps.isLoading = false;
});
builder.addCase(deleteTypistGroupAsync.rejected, (state) => {
state.apps.isLoading = false;
});
},
});

View File

@ -2,7 +2,7 @@ import { OPTION_ITEMS_DEFAULT_VALUE_TYPE } from "./constants";
// OPTION_ITEMS_DEFAULT_VALUE_TYPEからOptionItemDefaultValueTypeを作成する
export type OptionItemsDefaultValueType =
(typeof OPTION_ITEMS_DEFAULT_VALUE_TYPE)[keyof typeof OPTION_ITEMS_DEFAULT_VALUE_TYPE];
typeof OPTION_ITEMS_DEFAULT_VALUE_TYPE[keyof typeof OPTION_ITEMS_DEFAULT_VALUE_TYPE];
// 受け取った値がOptionItemDefaultValueType型かどうかを判定する
export const isOptionItemDefaultValueType = (

View File

@ -1,169 +0,0 @@
import React, { useCallback, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
selectInputValidationErrors,
selectFileDeleteSetting,
updateFileDeleteSettingAsync,
selectIsLoading,
getAccountRelationsAsync,
} from "features/account";
import { AppDispatch } from "app/store";
import { useTranslation } from "react-i18next";
import styles from "../../styles/app.module.scss";
import { getTranslationID } from "../../translation";
import close from "../../assets/images/close.svg";
import {
changeAutoFileDelete,
changeFileRetentionDays,
} from "../../features/account/accountSlice";
import progress_activit from "../../assets/images/progress_activit.svg";
interface FileDeleteSettingPopupProps {
// eslint-disable-next-line react/no-unused-prop-types
onClose: () => void;
}
export const FileDeleteSettingPopup: React.FC<FileDeleteSettingPopupProps> = (
props
) => {
const { onClose } = props;
const dispatch: AppDispatch = useDispatch();
const [t] = useTranslation();
const isLoading = useSelector(selectIsLoading);
const fileDeleteSetting = useSelector(selectFileDeleteSetting);
const { hasFileRetentionDaysError } = useSelector(
selectInputValidationErrors
);
const closePopup = useCallback(() => {
if (isLoading) return;
onClose();
}, [isLoading, onClose]);
const [isPushSubmitButton, setIsPushSubmitButton] = useState<boolean>(false);
const onUpdateFileDeleteSetting = useCallback(async () => {
if (isLoading) return;
setIsPushSubmitButton(true);
if (hasFileRetentionDaysError) {
return;
}
const { meta } = await dispatch(
updateFileDeleteSettingAsync({
autoFileDelete: fileDeleteSetting.autoFileDelete,
fileRetentionDays: fileDeleteSetting.fileRetentionDays,
})
);
setIsPushSubmitButton(false);
if (meta.requestStatus === "fulfilled") {
closePopup();
dispatch(getAccountRelationsAsync());
}
}, [
closePopup,
dispatch,
fileDeleteSetting.autoFileDelete,
fileDeleteSetting.fileRetentionDays,
hasFileRetentionDaysError,
isLoading,
]);
return (
<div className={`${styles.modal} ${styles.isShow}`}>
<div className={styles.modalBox}>
<p className={styles.modalTitle}>
{t(getTranslationID("fileDeleteSettingPopup.label.title"))}
<button type="button" onClick={closePopup}>
<img src={close} className={styles.modalTitleIcon} alt="close" />
</button>
</p>
<form className={styles.form}>
<dl className={`${styles.formList} ${styles.hasbg}`}>
<dt className={styles.formTitle} />
<dt>
{t(
getTranslationID(
"fileDeleteSettingPopup.label.autoFileDeleteCheck"
)
)}
</dt>
<dd className={styles.last}>
<p>
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
<label>
<input
type="checkbox"
className={styles.formCheck}
checked={fileDeleteSetting.autoFileDelete}
onChange={(e) => {
dispatch(
changeAutoFileDelete({
autoFileDelete: e.target.checked,
})
);
}}
/>
</label>
</p>
<p className={styles.txWsline}>
{t(
getTranslationID(
"fileDeleteSettingPopup.label.daysAnnotation"
)
)}
</p>
<input
type="number"
min={1}
max={999}
value={fileDeleteSetting.fileRetentionDays}
className={`${styles.formInput} ${styles.short}`}
disabled={!fileDeleteSetting.autoFileDelete}
onChange={(e) => {
dispatch(
changeFileRetentionDays({
fileRetentionDays: Number(e.target.value),
})
);
}}
/>{" "}
{t(getTranslationID("fileDeleteSettingPopup.label.days"))}
{isPushSubmitButton && hasFileRetentionDaysError && (
<span className={styles.formError}>
{t(
getTranslationID(
"fileDeleteSettingPopup.label.daysValidationError"
)
)}
</span>
)}
</dd>
<dd className={`${styles.full} ${styles.alignCenter}`}>
<input
type="button"
name="submit"
value={t(
getTranslationID("fileDeleteSettingPopup.label.saveButton")
)}
className={`${styles.formSubmit} ${styles.marginBtm1} ${
!isLoading ? styles.isActive : ""
}`}
onClick={onUpdateFileDeleteSetting}
disabled={isLoading}
/>
</dd>
<img
style={{ display: isLoading ? "inline" : "none" }}
src={progress_activit}
className={styles.icLoading}
alt="Loading"
/>
</dl>
</form>
</div>
</div>
);
};

View File

@ -1,4 +1,5 @@
import { AppDispatch } from "app/store";
import { UpdateTokenTimer } from "components/auth/updateTokenTimer";
import Footer from "components/footer";
import Header from "components/header";
import React, { useCallback, useEffect, useState } from "react";
@ -22,7 +23,6 @@ import { getTranslationID } from "translation";
import { TIERS } from "components/auth/constants";
import { isApproveTier } from "features/auth";
import { DeleteAccountPopup } from "./deleteAccountPopup";
import { FileDeleteSettingPopup } from "./fileDeleteSettingPopup";
import progress_activit from "../../assets/images/progress_activit.svg";
const AccountPage: React.FC = (): JSX.Element => {
@ -40,17 +40,10 @@ const AccountPage: React.FC = (): JSX.Element => {
const [isDeleteAccountPopupOpen, setIsDeleteAccountPopupOpen] =
useState(false);
const [isFileDeleteSettingPopupOpen, setIsFileDeleteSettingPopupOpen] =
useState(false);
const onDeleteAccountOpen = useCallback(() => {
setIsDeleteAccountPopupOpen(true);
}, [setIsDeleteAccountPopupOpen]);
const onDeleteFileDeleteSettingOpen = useCallback(() => {
setIsFileDeleteSettingPopupOpen(true);
}, [setIsFileDeleteSettingPopupOpen]);
// 階層表示用
const tierNames: { [key: number]: string } = {
// eslint-disable-next-line @typescript-eslint/naming-convention
@ -96,15 +89,9 @@ const AccountPage: React.FC = (): JSX.Element => {
}}
/>
)}
{isFileDeleteSettingPopupOpen && (
<FileDeleteSettingPopup
onClose={() => {
setIsFileDeleteSettingPopupOpen(false);
}}
/>
)}
<div className={styles.wrap}>
<Header />
<UpdateTokenTimer />
<main className={styles.main}>
<div>
<div className={styles.pageHeader}>
@ -115,13 +102,12 @@ const AccountPage: React.FC = (): JSX.Element => {
<section className={styles.account}>
<div className={styles.boxFlex}>
{/* File Delete Setting
<ul className={`${styles.menuAction} ${styles.box100}`}>
<li>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
<a
href="account_setting.html"
className={`${styles.menuLink} ${styles.isActive}`}
onClick={onDeleteFileDeleteSettingOpen}
data-tag="open-file-delete-setting-popup"
>
<img
src="images/file_delete.svg"
@ -134,6 +120,7 @@ const AccountPage: React.FC = (): JSX.Element => {
</a>
</li>
</ul>
*/}
<div className={styles.marginRgt3}>
<dl className={styles.listVertical}>
@ -171,7 +158,7 @@ const AccountPage: React.FC = (): JSX.Element => {
changeDealer({
parentAccountId:
dealers.find(
(x) => x.id === Number(event.target.value)
(x) => x.name === event.target.value
)?.id || undefined,
})
);
@ -188,7 +175,7 @@ const AccountPage: React.FC = (): JSX.Element => {
)} --`}
</option>
{dealers.map((x) => (
<option key={x.id} value={x.id}>
<option key={x.name} value={x.name}>
{x.name}
</option>
))}
@ -229,23 +216,9 @@ const AccountPage: React.FC = (): JSX.Element => {
)
)}
</dd>
<dd
style={{ paddingBottom: 0 }}
className={`${styles.full}`}
/>
</>
)}
{!isTier5 && <dd>-</dd>}
<dt>
{t(
getTranslationID("accountPage.label.fileRetentionDays")
)}
</dt>
<dd>
{viewInfo.account.autoFileDelete
? viewInfo.account.fileRetentionDays
: "-"}
</dd>
</dl>
</div>

View File

@ -1,15 +1,13 @@
import React, { useCallback, useEffect, useState } from "react";
import React, { useCallback } from "react";
import styles from "styles/app.module.scss";
import { useDispatch, useSelector } from "react-redux";
import { useSelector } from "react-redux";
import {
selectSelectedFileTask,
selectIsLoading,
PRIORITY,
renameFileAsync,
} from "features/dictation";
import { getTranslationID } from "translation";
import { useTranslation } from "react-i18next";
import { AppDispatch } from "app/store";
import close from "../../assets/images/close.svg";
import lock from "../../assets/images/lock.svg";
@ -21,55 +19,14 @@ interface FilePropertyPopupProps {
export const FilePropertyPopup: React.FC<FilePropertyPopupProps> = (props) => {
const { onClose, isOpen } = props;
const [t] = useTranslation();
const dispatch: AppDispatch = useDispatch();
const isLoading = useSelector(selectIsLoading);
const [isPushSaveButton, setIsPushSaveButton] = useState<boolean>(false);
// ポップアップを閉じる処理
const closePopup = useCallback(() => {
setIsPushSaveButton(false);
onClose(false);
}, [onClose]);
const selectedFileTask = useSelector(selectSelectedFileTask);
const [fileName, setFileName] = useState<string>("");
useEffect(() => {
if (isOpen) {
setFileName(selectedFileTask?.fileName ?? "");
}
}, [selectedFileTask, isOpen]);
// ファイル名の保存処理
const saveFileName = useCallback(async () => {
setIsPushSaveButton(true);
if (fileName.length === 0) {
return;
}
// ダイアログ確認
if (
/* eslint-disable-next-line no-alert */
!window.confirm(t(getTranslationID("common.message.dialogConfirm")))
) {
return;
}
const { meta } = await dispatch(
renameFileAsync({
audioFileId: selectedFileTask?.audioFileId ?? 0,
fileName,
})
);
setIsPushSaveButton(false);
if (meta.requestStatus === "fulfilled") {
onClose(true);
}
}, [t, dispatch, onClose, fileName, selectedFileTask]);
return (
<div className={`${styles.modal} ${isOpen ? styles.isShow : ""}`}>
<div className={styles.modalBox}>
@ -88,41 +45,7 @@ export const FilePropertyPopup: React.FC<FilePropertyPopupProps> = (props) => {
{t(getTranslationID("filePropertyPopup.label.general"))}
</dt>
<dt>{t(getTranslationID("dictationPage.label.fileName"))}</dt>
<dd className={styles.hasInput}>
<input
type="text"
size={40}
maxLength={64}
value={fileName}
className={`${styles.formInput} ${styles.short} ${
isPushSaveButton && fileName.length === 0 && styles.isError
}`}
onChange={(e) => setFileName(e.target.value)}
/>
<input
type="button"
name="submit"
value={t(getTranslationID("dictationPage.label.fileNameSave"))}
className={`${styles.formSubmit} ${styles.isActive}`}
style={{
position: "relative",
marginTop: "0.2rem",
right: "auto",
maxWidth: "18rem",
whiteSpace: "normal",
overflowWrap: "break-word",
fontSize: "small",
}}
onClick={saveFileName}
/>
{isPushSaveButton && fileName.length === 0 && (
<span className={styles.formError}>
{t(getTranslationID("common.message.inputEmptyError"))}
</span>
)}
</dd>
<dt>{t(getTranslationID("dictationPage.label.rawFileName"))}</dt>
<dd>{selectedFileTask?.rawFileName ?? ""}</dd>
<dd>{selectedFileTask?.fileName.replace(".zip", "") ?? ""}</dd>
<dt>{t(getTranslationID("dictationPage.label.fileSize"))}</dt>
<dd>{selectedFileTask?.fileSize ?? ""}</dd>
<dt>{t(getTranslationID("dictationPage.label.fileLength"))}</dt>

File diff suppressed because it is too large Load Diff

View File

@ -2,10 +2,6 @@ import React, { useState, useCallback, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { AppDispatch } from "app/store";
import { useDispatch, useSelector } from "react-redux";
import {
clearHierarchicalElement,
getMyAccountAsync,
} from "features/license/partnerLicense";
import styles from "../../styles/app.module.scss";
import { getTranslationID } from "../../translation";
import close from "../../assets/images/close.svg";
@ -96,8 +92,6 @@ export const CardLicenseIssuePopup: React.FC<CardLicenseIssuePopupProps> = (
setIsPushCreateButton(false);
if (meta.requestStatus === "fulfilled") {
dispatch(getMyAccountAsync());
dispatch(clearHierarchicalElement());
closePopup();
}
}, [

Some files were not shown because too many files have changed in this diff Show More