Compare commits
244 Commits
release-20
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d9a254b63 | ||
|
|
ff5533b647 | ||
|
|
b3845187f6 | ||
|
|
019c818a19 | ||
|
|
1137d826ae | ||
|
|
b529388871 | ||
|
|
aef17893d9 | ||
|
|
4f598b0017 | ||
|
|
b71ec627d7 | ||
|
|
af56f8ccad | ||
|
|
0b451ed62f | ||
|
|
11395279af | ||
|
|
a07cfe51aa | ||
|
|
ad397f6fe7 | ||
|
|
85fdec2e5a | ||
|
|
f1b75a7ff0 | ||
|
|
6690302ac3 | ||
|
|
1320222d79 | ||
|
|
baea8ce5e5 | ||
|
|
d69126a980 | ||
|
|
79a2b9f0a3 | ||
|
|
2a755f2bd3 | ||
|
|
edb8a79f94 | ||
|
|
4a5136deee | ||
|
|
f2eaba7e5f | ||
|
|
3b576c6a47 | ||
|
|
557fc48d05 | ||
|
|
353d5ad462 | ||
|
|
df55be5e19 | ||
|
|
35425c576a | ||
|
|
d4abc0fcfb | ||
|
|
11c0937214 | ||
|
|
12fe7ac9bb | ||
|
|
b997b928b8 | ||
|
|
10f57c6aeb | ||
|
|
254cbdf0d6 | ||
|
|
6b1286ba13 | ||
|
|
aea66f4616 | ||
|
|
c73340ec51 | ||
|
|
07904c35e6 | ||
|
|
474cdd56c6 | ||
|
|
83732fa1b5 | ||
|
|
6b1650a634 | ||
|
|
4f7d65f0e8 | ||
|
|
1f10a3f096 | ||
|
|
909c2a6d55 | ||
|
|
37666a00c7 | ||
|
|
b7925f311b | ||
|
|
97620881d3 | ||
|
|
0e68f26c57 | ||
|
|
9736d23653 | ||
|
|
261a5afbdd | ||
|
|
3a7c1765ee | ||
|
|
c16a8b023c | ||
|
|
4b16e6a004 | ||
|
|
bd8f035c46 | ||
|
|
bdcce37b5a | ||
|
|
7af5c50a27 | ||
|
|
5860da285e | ||
|
|
3ece576476 | ||
|
|
4dac420bed | ||
|
|
ddf338bc72 | ||
|
|
0cca61517c | ||
|
|
fe5e8b8e1c | ||
|
|
dfdc6a33ad | ||
|
|
35e2d626a0 | ||
|
|
228e21ba78 | ||
|
|
68df7cd728 | ||
|
|
ffd6eb4e68 | ||
|
|
e205209b12 | ||
|
|
3ea84a3597 | ||
|
|
b71c4398d2 | ||
|
|
c26ad130ed | ||
|
|
279a9ab037 | ||
|
|
4e00c03ef2 | ||
|
|
c813ddc0ac | ||
|
|
23f8b54011 | ||
|
|
8122f6f4e1 | ||
|
|
e76242bddd | ||
|
|
af0ba78ae9 | ||
|
|
1904b95adf | ||
|
|
d43cece48a | ||
|
|
ba7196cac1 | ||
|
|
7eecb001c6 | ||
|
|
0b01da936d | ||
|
|
b88c0d9b96 | ||
|
|
b7554e30ff | ||
|
|
b03cda3ccc | ||
|
|
7bfd424a64 | ||
|
|
9ee29e91ba | ||
|
|
b24059b538 | ||
|
|
a7b18d8151 | ||
|
|
23862ad3ac | ||
|
|
566da623bf | ||
|
|
69241ed36c | ||
|
|
c469f943f1 | ||
|
|
0a714f8484 | ||
|
|
1d2089b0c4 | ||
|
|
f975ecf551 | ||
|
|
e6d27d7810 | ||
|
|
f209c7359e | ||
|
|
33d4ab3d2f | ||
|
|
07bca1d638 | ||
|
|
09c21eafa7 | ||
|
|
e6d6e477d9 | ||
|
|
915483c109 | ||
|
|
c600d9f818 | ||
|
|
5147f853ae | ||
|
|
0288292058 | ||
|
|
8752448eed | ||
|
|
1d71bef7aa | ||
|
|
114ded790e | ||
|
|
ae638b16be | ||
|
|
133db833ee | ||
|
|
1f0cf50166 | ||
|
|
eb6b413adb | ||
|
|
6e93a5be79 | ||
|
|
ac3d523c0e | ||
|
|
75f0a49fc1 | ||
|
|
cab7a75ec1 | ||
|
|
b8af6fb6b2 | ||
|
|
13e0793219 | ||
|
|
f80912c617 | ||
|
|
66c643677d | ||
|
|
1451d6f584 | ||
|
|
5f4a05044b | ||
|
|
02a4784e58 | ||
|
|
9256566f89 | ||
|
|
9b61443b2f | ||
|
|
5169092892 | ||
|
|
017276c94a | ||
|
|
e96e8ea54a | ||
|
|
2b68a9f054 | ||
|
|
83e297cc9b | ||
|
|
9f5ccabb0c | ||
|
|
415fd2eb58 | ||
|
|
340aa73bde | ||
|
|
43561f237e | ||
|
|
311eb98236 | ||
|
|
8b2613b9f6 | ||
|
|
071bd2b85e | ||
|
|
1ef696efe8 | ||
|
|
c059a2eabd | ||
|
|
84fc89071a | ||
|
|
ccc03da62d | ||
|
|
ff4cd35ed3 | ||
|
|
f386a8f7e0 | ||
|
|
2e6b7c8ab5 | ||
|
|
85edd2296e | ||
|
|
869cbd43e0 | ||
|
|
aca9bcf496 | ||
|
|
146b8a6e40 | ||
|
|
9dc80b4965 | ||
|
|
04c726e964 | ||
|
|
f265d0ff45 | ||
|
|
e448e8d249 | ||
|
|
eda88aa048 | ||
|
|
da40e8f09c | ||
|
|
d6a47932e7 | ||
|
|
7160e0ee2e | ||
|
|
2220e2560f | ||
|
|
31de71f743 | ||
|
|
7ff563f644 | ||
|
|
a47ebaa9df | ||
|
|
88ce6a2c9e | ||
|
|
cad3a99f70 | ||
|
|
0ebd2ab17e | ||
|
|
9ca9b7a144 | ||
|
|
fd3e584fac | ||
|
|
ce6e09a7d0 | ||
|
|
6d56255a5a | ||
|
|
363f12f86f | ||
|
|
0be9c26f09 | ||
|
|
71127a6db9 | ||
|
|
f6d39a4c26 | ||
|
|
34d1dd5629 | ||
|
|
f0d71937e3 | ||
|
|
dd8bddc971 | ||
|
|
ddd4d31f25 | ||
|
|
5305984b1a | ||
|
|
0ab6488f58 | ||
|
|
5a78a6668f | ||
|
|
d0628caa05 | ||
|
|
68d1a1796b | ||
|
|
c95fb1e1f6 | ||
|
|
c1f370faaf | ||
|
|
13d421c2bc | ||
|
|
b524fd5995 | ||
|
|
a1b59de44d | ||
|
|
fbdfeee73c | ||
|
|
f03342bc55 | ||
|
|
a65d6a2774 | ||
|
|
cb68c16eb8 | ||
|
|
c31bb47bb8 | ||
|
|
dc52ec2022 | ||
|
|
439ce7de63 | ||
|
|
f8183399e2 | ||
|
|
e44cb3b955 | ||
|
|
562db3def9 | ||
|
|
ecb28b9328 | ||
|
|
cdef84e269 | ||
|
|
fc7d271a29 | ||
|
|
aef30c8cbe | ||
|
|
c0b99203da | ||
|
|
63191a3b61 | ||
|
|
3877a4670d | ||
|
|
bbbd3e757b | ||
|
|
83efd97bdf | ||
|
|
447b0e280c | ||
|
|
cd277f3f9a | ||
|
|
270122b135 | ||
|
|
32d8c6b896 | ||
|
|
91c27b7684 | ||
|
|
548c7a05e9 | ||
|
|
eaf1b3c8b8 | ||
|
|
19b544540e | ||
|
|
feeec9d1f5 | ||
|
|
4442c7cbbe | ||
|
|
1e8bc39c7f | ||
|
|
ce3deecd1b | ||
|
|
8e159a1c2a | ||
|
|
df74dc358c | ||
|
|
186896da15 | ||
|
|
4be13e002d | ||
|
|
cf30b97a16 | ||
|
|
44759b1aac | ||
|
|
5ab2f02c56 | ||
|
|
f595cae1b8 | ||
|
|
48a2bddfd9 | ||
|
|
fbcafd2014 | ||
|
|
9f7c8c99c0 | ||
|
|
92193d499a | ||
|
|
c32b38b783 | ||
|
|
08a37ff264 | ||
|
|
f00861702a | ||
|
|
8dfbcea0da | ||
|
|
8aa45baee8 | ||
|
|
f2f8728319 | ||
|
|
ded446de93 | ||
|
|
1524ec2473 | ||
|
|
a7bb32ec4a | ||
|
|
d08c6c99af | ||
|
|
8793606070 | ||
|
|
81c299dd99 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
environment_building_tools/logfile.log
|
||||
312
azure-pipelines-staging-ccb.yml
Normal file
312
azure-pipelines-staging-ccb.yml
Normal file
@ -0,0 +1,312 @@
|
||||
# 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
|
||||
363
azure-pipelines-staging-ph1-enhance.yml
Normal file
363
azure-pipelines-staging-ph1-enhance.yml
Normal file
@ -0,0 +1,363 @@
|
||||
# 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
|
||||
@ -43,11 +43,14 @@ jobs:
|
||||
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
|
||||
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')
|
||||
@ -170,9 +173,32 @@ jobs:
|
||||
--type block \
|
||||
--overwrite \
|
||||
--file $(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip
|
||||
- job: function_build
|
||||
- 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
|
||||
@ -186,32 +212,6 @@ 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:
|
||||
|
||||
@ -1,70 +0,0 @@
|
||||
/*
|
||||
エラーコード作成方針
|
||||
E+6桁(数字)で構成する。
|
||||
- 1~2桁目の値は種類(業務エラー、システムエラー...)
|
||||
- 3~4桁目の値は原因箇所(トークン、DB、...)
|
||||
- 5~6桁目の値は任意の重複しない値
|
||||
ex)
|
||||
E00XXXX : システムエラー(通信エラーやDB接続失敗など)
|
||||
E01XXXX : 業務エラー
|
||||
EXX00XX : 内部エラー(内部プログラムのエラー)
|
||||
EXX01XX : トークンエラー(トークン認証関連)
|
||||
EXX02XX : DBエラー(DB関連)
|
||||
EXX03XX : ADB2Cエラー(DB関連)
|
||||
*/
|
||||
export const ErrorCodes = [
|
||||
'E009999', // 汎用エラー
|
||||
'E000101', // トークン形式不正エラー
|
||||
'E000102', // トークン有効期限切れエラー
|
||||
'E000103', // トークン非アクティブエラー
|
||||
'E000104', // トークン署名エラー
|
||||
'E000105', // トークン発行元エラー
|
||||
'E000106', // トークンアルゴリズムエラー
|
||||
'E000107', // トークン不足エラー
|
||||
'E000108', // トークン権限エラー
|
||||
'E000301', // ADB2Cへのリクエスト上限超過エラー
|
||||
'E000401', // IPアドレス未設定エラー
|
||||
'E000501', // リクエストID未設定エラー
|
||||
'E010001', // パラメータ形式不正エラー
|
||||
'E010201', // 未認証ユーザエラー
|
||||
'E010202', // 認証済ユーザエラー
|
||||
'E010203', // 管理ユーザ権限エラー
|
||||
'E010204', // ユーザ不在エラー
|
||||
'E010205', // DBのRoleが想定外の値エラー
|
||||
'E010206', // DBのTierが想定外の値エラー
|
||||
'E010207', // ユーザーのRole変更不可エラー
|
||||
'E010208', // ユーザーの暗号化パスワード不足エラー
|
||||
'E010209', // ユーザーの同意済み利用規約バージョンが最新でないエラー
|
||||
'E010301', // メールアドレス登録済みエラー
|
||||
'E010302', // authorId重複エラー
|
||||
'E010401', // PONumber重複エラー
|
||||
'E010501', // アカウント不在エラー
|
||||
'E010502', // アカウント情報変更不可エラー
|
||||
'E010503', // 代行操作不許可エラー
|
||||
'E010504', // アカウントロックエラー
|
||||
'E010601', // タスク変更不可エラー(タスクが変更できる状態でない、またはタスクが存在しない)
|
||||
'E010602', // タスク変更権限不足エラー
|
||||
'E010603', // タスク不在エラー
|
||||
'E010701', // Blobファイル不在エラー
|
||||
'E010801', // ライセンス不在エラー
|
||||
'E010802', // ライセンス取り込み済みエラー
|
||||
'E010803', // ライセンス発行済みエラー
|
||||
'E010804', // ライセンス不足エラー
|
||||
'E010805', // ライセンス有効期限切れエラー
|
||||
'E010806', // ライセンス割り当て不可エラー
|
||||
'E010807', // ライセンス割り当て解除済みエラー
|
||||
'E010808', // ライセンス注文キャンセル不可エラー
|
||||
'E010809', // ライセンス発行キャンセル不可エラー(ステータスが変えられている場合)
|
||||
'E010810', // ライセンス発行キャンセル不可エラー(発行から一定期間経過した場合)
|
||||
'E010811', // ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合)
|
||||
'E010812', // ライセンス未割当エラー
|
||||
'E010908', // タイピストグループ不在エラー
|
||||
'E010909', // タイピストグループ名重複エラー
|
||||
'E011001', // ワークタイプ重複エラー
|
||||
'E011002', // ワークタイプ登録上限超過エラー
|
||||
'E011003', // ワークタイプ不在エラー
|
||||
'E011004', // ワークタイプ使用中エラー
|
||||
'E012001', // テンプレートファイル不在エラー
|
||||
'E013001', // ワークフローのAuthorIDとWorktypeIDのペア重複エラー
|
||||
'E013002', // ワークフロー不在エラー
|
||||
] as const;
|
||||
@ -1,10 +0,0 @@
|
||||
import { errors } from './message';
|
||||
import { ErrorCodeType, ErrorResponse } from './types/types';
|
||||
|
||||
export const makeErrorResponse = (errorcode: ErrorCodeType): ErrorResponse => {
|
||||
const msg = errors[errorcode];
|
||||
return {
|
||||
code: errorcode,
|
||||
message: msg,
|
||||
};
|
||||
};
|
||||
@ -1,59 +0,0 @@
|
||||
import { Errors } from './types/types';
|
||||
|
||||
// エラーコードとメッセージ対応表
|
||||
export const errors: Errors = {
|
||||
E009999: 'Internal Server Error.',
|
||||
E000101: 'Token invalid format Error.',
|
||||
E000102: 'Token expired Error.',
|
||||
E000103: 'Token not before Error',
|
||||
E000104: 'Token invalid signature Error.',
|
||||
E000105: 'Token invalid issuer Error.',
|
||||
E000106: 'Token invalid algorithm Error.',
|
||||
E000107: 'Token is not exist Error.',
|
||||
E000108: 'Token authority failed Error.',
|
||||
E000301: 'ADB2C request limit exceeded Error',
|
||||
E000401: 'IP address not found Error.',
|
||||
E000501: 'Request ID not found Error.',
|
||||
E010001: 'Param invalid format Error.',
|
||||
E010201: 'Email not verified user Error.',
|
||||
E010202: 'Email already verified user Error.',
|
||||
E010203: 'Administrator Permissions Error.',
|
||||
E010204: 'User not Found Error.',
|
||||
E010205: 'Role from DB is unexpected value Error.',
|
||||
E010206: 'Tier from DB is unexpected value Error.',
|
||||
E010207: 'User role change not allowed Error.',
|
||||
E010208: 'User encryption password not found Error.',
|
||||
E010209: 'Accepted term not latest Error.',
|
||||
E010301: 'This email user already created Error',
|
||||
E010302: 'This AuthorId already used Error',
|
||||
E010401: 'This PoNumber already used Error',
|
||||
E010501: 'Account not Found Error.',
|
||||
E010502: 'Account information cannot be changed Error.',
|
||||
E010503: 'Delegation not allowed Error.',
|
||||
E010504: 'Account is locked Error.',
|
||||
E010601: 'Task is not Editable Error',
|
||||
E010602: 'No task edit permissions Error',
|
||||
E010603: 'Task not found Error.',
|
||||
E010701: 'File not found in Blob Storage Error.',
|
||||
E010801: 'License not exist Error',
|
||||
E010802: 'License already activated Error',
|
||||
E010803: 'License already issued Error',
|
||||
E010804: 'License shortage Error',
|
||||
E010805: 'License is expired Error',
|
||||
E010806: 'License is unavailable Error',
|
||||
E010807: 'License is already deallocated Error',
|
||||
E010808: 'Order cancel failed Error',
|
||||
E010809: 'Already license order status changed Error',
|
||||
E010810: 'Cancellation period expired error',
|
||||
E010811: 'Already license allocated Error',
|
||||
E010812: 'License not allocated Error',
|
||||
E010908: 'Typist Group not exist Error',
|
||||
E010909: 'Typist Group name already exist Error',
|
||||
E011001: 'This WorkTypeID already used Error',
|
||||
E011002: 'WorkTypeID create limit exceeded Error',
|
||||
E011003: 'WorkTypeID not found Error',
|
||||
E011004: 'WorkTypeID is in use Error',
|
||||
E012001: 'Template file not found Error',
|
||||
E013001: 'AuthorId and WorktypeId pair already exists Error',
|
||||
E013002: 'Workflow not found Error',
|
||||
};
|
||||
@ -1,15 +0,0 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { ErrorCodes } from '../code';
|
||||
|
||||
export class ErrorResponse {
|
||||
@ApiProperty()
|
||||
message: string;
|
||||
@ApiProperty()
|
||||
code: string;
|
||||
}
|
||||
|
||||
export type ErrorCodeType = (typeof ErrorCodes)[number];
|
||||
|
||||
export type Errors = {
|
||||
[P in ErrorCodeType]: string;
|
||||
};
|
||||
78
data_migration_tools/server/package-lock.json
generated
78
data_migration_tools/server/package-lock.json
generated
@ -26,6 +26,7 @@
|
||||
"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",
|
||||
@ -3107,9 +3108,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.8.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
|
||||
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
|
||||
"version": "8.11.3",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
|
||||
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
|
||||
"devOptional": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
@ -4049,6 +4050,35 @@
|
||||
"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",
|
||||
@ -8654,6 +8684,11 @@
|
||||
"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",
|
||||
@ -12333,9 +12368,9 @@
|
||||
}
|
||||
},
|
||||
"acorn": {
|
||||
"version": "8.8.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
|
||||
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
|
||||
"version": "8.11.3",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
|
||||
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
|
||||
"devOptional": true
|
||||
},
|
||||
"acorn-import-assertions": {
|
||||
@ -13027,6 +13062,32 @@
|
||||
"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",
|
||||
@ -16557,6 +16618,11 @@
|
||||
"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",
|
||||
|
||||
@ -45,7 +45,8 @@
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rxjs": "^7.8.0",
|
||||
"swagger-cli": "^4.0.4",
|
||||
"typeorm": "^0.3.20"
|
||||
"typeorm": "^0.3.20",
|
||||
"csv": "^6.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.17",
|
||||
|
||||
@ -24,6 +24,12 @@ import { DeleteModule } from "./features/delete/delete.module";
|
||||
import { DeleteRepositoryModule } from "./repositories/delete/delete.repository.module";
|
||||
import { DeleteController } from "./features/delete/delete.controller";
|
||||
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: [
|
||||
@ -37,7 +43,9 @@ import { DeleteService } from "./features/delete/delete.service";
|
||||
AdB2cModule,
|
||||
AccountsModule,
|
||||
UsersModule,
|
||||
TransferModule,
|
||||
RegisterModule,
|
||||
VerificationModule,
|
||||
AccountsRepositoryModule,
|
||||
UsersRepositoryModule,
|
||||
SortCriteriaRepositoryModule,
|
||||
@ -61,8 +69,22 @@ import { DeleteService } from "./features/delete/delete.service";
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
],
|
||||
controllers: [RegisterController, AccountsController, UsersController, DeleteController],
|
||||
providers: [RegisterService, AccountsService, UsersService, DeleteService],
|
||||
controllers: [
|
||||
RegisterController,
|
||||
AccountsController,
|
||||
UsersController,
|
||||
DeleteController,
|
||||
TransferController,
|
||||
VerificationController,
|
||||
],
|
||||
providers: [
|
||||
RegisterService,
|
||||
AccountsService,
|
||||
UsersService,
|
||||
DeleteService,
|
||||
TransferService,
|
||||
VerificationService,
|
||||
],
|
||||
})
|
||||
export class AppModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
|
||||
@ -18,7 +18,8 @@ export const makePassword = (): string => {
|
||||
let autoGeneratedPassword: string = "";
|
||||
|
||||
while (!valid) {
|
||||
// パスワードをランダムに決定
|
||||
autoGeneratedPassword = "";
|
||||
// パスワードをランダムに決定+
|
||||
while (autoGeneratedPassword.length < passLength) {
|
||||
// 上で決定したcharsの中からランダムに1文字ずつ追加
|
||||
const index = Math.floor(Math.random() * chars.length);
|
||||
@ -30,6 +31,11 @@ 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;
|
||||
};
|
||||
|
||||
@ -8,8 +8,8 @@ export class csvInputFile {
|
||||
last_name: string;
|
||||
country: string;
|
||||
state: string;
|
||||
start_date: Date;
|
||||
expired_date: Date;
|
||||
start_date: string;
|
||||
expired_date: string;
|
||||
user_email: string;
|
||||
author_id: string;
|
||||
recording_mode: string;
|
||||
@ -34,7 +34,12 @@ export class csvInputFile {
|
||||
wt19: string;
|
||||
wt20: string;
|
||||
}
|
||||
export class AccountsOutputFileStep1 {
|
||||
|
||||
export class csvInputFileWithRow extends csvInputFile {
|
||||
row: number;
|
||||
}
|
||||
|
||||
export class AccountsFileType {
|
||||
accountId: number;
|
||||
type: string;
|
||||
companyName: string;
|
||||
@ -43,9 +48,11 @@ export class AccountsOutputFileStep1 {
|
||||
adminName: string;
|
||||
adminMail: string;
|
||||
userId: number;
|
||||
role: string;
|
||||
authorId: string;
|
||||
}
|
||||
|
||||
export class AccountsOutputFile {
|
||||
export class AccountsFile {
|
||||
accountId: number;
|
||||
type: number;
|
||||
companyName: string;
|
||||
@ -54,18 +61,11 @@ export class AccountsOutputFile {
|
||||
adminName: string;
|
||||
adminMail: string;
|
||||
userId: number;
|
||||
role: string;
|
||||
authorId: string;
|
||||
}
|
||||
export class AccountsInputFile {
|
||||
accountId: number;
|
||||
type: number;
|
||||
companyName: string;
|
||||
country: string;
|
||||
dealerAccountId?: number;
|
||||
adminName: string;
|
||||
adminMail: string;
|
||||
userId: number;
|
||||
}
|
||||
export class UsersOutputFile {
|
||||
|
||||
export class UsersFile {
|
||||
accountId: number;
|
||||
userId: number;
|
||||
name: string;
|
||||
@ -74,23 +74,7 @@ export class UsersOutputFile {
|
||||
email: string;
|
||||
}
|
||||
|
||||
export class UsersInputFile {
|
||||
accountId: number;
|
||||
userId: number;
|
||||
name: string;
|
||||
role: string;
|
||||
authorId: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export class LicensesOutputFile {
|
||||
expiry_date: string;
|
||||
account_id: number;
|
||||
type: string;
|
||||
status: string;
|
||||
allocated_user_id?: number;
|
||||
}
|
||||
export class LicensesInputFile {
|
||||
export class LicensesFile {
|
||||
expiry_date: string;
|
||||
account_id: number;
|
||||
type: string;
|
||||
@ -98,16 +82,12 @@ export class LicensesInputFile {
|
||||
allocated_user_id?: number;
|
||||
}
|
||||
|
||||
export class WorktypesOutputFile {
|
||||
account_id: number;
|
||||
custom_worktype_id: string;
|
||||
}
|
||||
export class WorktypesInputFile {
|
||||
export class WorktypesFile {
|
||||
account_id: number;
|
||||
custom_worktype_id: string;
|
||||
}
|
||||
|
||||
export class CardLicensesInputFile {
|
||||
export class CardLicensesFile {
|
||||
license_id: number;
|
||||
issue_id: number;
|
||||
card_license_key: string;
|
||||
@ -118,10 +98,26 @@ export class CardLicensesInputFile {
|
||||
updated_by?: string;
|
||||
}
|
||||
|
||||
export function isAccountsInputFileArray(obj: any): obj is AccountsInputFile[] {
|
||||
return Array.isArray(obj) && obj.every((item) => isAccountsInputFile(item));
|
||||
export class AccountsMappingFile {
|
||||
accountIdText: string;
|
||||
accountIdNumber: number;
|
||||
}
|
||||
export function isAccountsInputFile(obj: any): obj is AccountsInputFile {
|
||||
|
||||
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));
|
||||
}
|
||||
export function isAccountsFile(obj: any): obj is AccountsFile {
|
||||
return (
|
||||
typeof obj === "object" &&
|
||||
obj !== null &&
|
||||
@ -134,21 +130,27 @@ export function isAccountsInputFile(obj: any): obj is AccountsInputFile {
|
||||
"country" in obj &&
|
||||
typeof obj.country === "string" &&
|
||||
("dealerAccountId" in obj
|
||||
? typeof obj.dealerAccountId === "number"
|
||||
? obj.dealerAccountId === null || typeof obj.dealerAccountId === "number"
|
||||
: true) &&
|
||||
"adminName" in obj &&
|
||||
typeof obj.adminName === "string" &&
|
||||
"adminMail" in obj &&
|
||||
typeof obj.adminMail === "string" &&
|
||||
"userId" in obj &&
|
||||
typeof obj.userId === "number"
|
||||
typeof obj.userId === "number" &&
|
||||
("role" in obj
|
||||
? obj.role === null || typeof obj.role === "string"
|
||||
: true) &&
|
||||
("authorId" in obj
|
||||
? obj.authorId === null || typeof obj.authorId === "string"
|
||||
: true)
|
||||
);
|
||||
}
|
||||
|
||||
export function isUsersInputFileArray(obj: any): obj is UsersInputFile[] {
|
||||
return Array.isArray(obj) && obj.every((item) => isUsersInputFile(item));
|
||||
export function isUsersFileArray(obj: any): obj is UsersFile[] {
|
||||
return Array.isArray(obj) && obj.every((item) => isUsersFile(item));
|
||||
}
|
||||
export function isUsersInputFile(obj: any): obj is UsersInputFile {
|
||||
export function isUsersFile(obj: any): obj is UsersFile {
|
||||
return (
|
||||
typeof obj === "object" &&
|
||||
obj !== null &&
|
||||
@ -167,10 +169,10 @@ export function isUsersInputFile(obj: any): obj is UsersInputFile {
|
||||
);
|
||||
}
|
||||
|
||||
export function isLicensesInputFileArray(obj: any): obj is LicensesInputFile[] {
|
||||
return Array.isArray(obj) && obj.every((item) => isLicensesInputFile(item));
|
||||
export function isLicensesFileArray(obj: any): obj is LicensesFile[] {
|
||||
return Array.isArray(obj) && obj.every((item) => isLicensesFile(item));
|
||||
}
|
||||
export function isLicensesInputFile(obj: any): obj is LicensesInputFile {
|
||||
export function isLicensesFile(obj: any): obj is LicensesFile {
|
||||
return (
|
||||
typeof obj === "object" &&
|
||||
obj !== null &&
|
||||
@ -187,12 +189,10 @@ export function isLicensesInputFile(obj: any): obj is LicensesInputFile {
|
||||
);
|
||||
}
|
||||
|
||||
export function isWorktypesInputFileArray(
|
||||
obj: any
|
||||
): obj is WorktypesInputFile[] {
|
||||
return Array.isArray(obj) && obj.every((item) => isWorktypesInputFile(item));
|
||||
export function isWorktypesFileArray(obj: any): obj is WorktypesFile[] {
|
||||
return Array.isArray(obj) && obj.every((item) => isWorktypesFile(item));
|
||||
}
|
||||
export function isWorktypesInputFile(obj: any): obj is WorktypesInputFile {
|
||||
export function isWorktypesFile(obj: any): obj is WorktypesFile {
|
||||
return (
|
||||
typeof obj === "object" &&
|
||||
obj !== null &&
|
||||
@ -203,16 +203,10 @@ export function isWorktypesInputFile(obj: any): obj is WorktypesInputFile {
|
||||
);
|
||||
}
|
||||
|
||||
export function isCardLicensesInputFileArray(
|
||||
obj: any
|
||||
): obj is CardLicensesInputFile[] {
|
||||
return (
|
||||
Array.isArray(obj) && obj.every((item) => isCardLicensesInputFile(item))
|
||||
);
|
||||
export function isCardLicensesFileArray(obj: any): obj is CardLicensesFile[] {
|
||||
return Array.isArray(obj) && obj.every((item) => isCardLicensesFile(item));
|
||||
}
|
||||
export function isCardLicensesInputFile(
|
||||
obj: any
|
||||
): obj is CardLicensesInputFile {
|
||||
export function isCardLicensesFile(obj: any): obj is CardLicensesFile {
|
||||
return (
|
||||
typeof obj === "object" &&
|
||||
obj !== null &&
|
||||
@ -229,3 +223,65 @@ export function isCardLicensesInputFile(
|
||||
(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
|
||||
);
|
||||
}
|
||||
|
||||
@ -343,7 +343,7 @@ export const MIGRATION_TYPE = {
|
||||
export const COUNTRY_LIST = [
|
||||
{ value: "CA", label: "Canada" },
|
||||
{ value: "KY", label: "Cayman Islands" },
|
||||
{ value: "US", label: "U.S.A." },
|
||||
{ value: "US", label: "United States" },
|
||||
{ value: "AU", label: "Australia" },
|
||||
{ value: "NZ", label: "New Zealand" },
|
||||
{ value: "AT", label: "Austria" },
|
||||
@ -351,7 +351,7 @@ export const COUNTRY_LIST = [
|
||||
{ value: "BG", label: "Bulgaria" },
|
||||
{ value: "HR", label: "Croatia" },
|
||||
{ value: "CY", label: "Cyprus" },
|
||||
{ value: "CZ", label: "Czech Republic" },
|
||||
{ value: "CZ", label: "Czech" },
|
||||
{ value: "DK", label: "Denmark" },
|
||||
{ value: "EE", label: "Estonia" },
|
||||
{ value: "FI", label: "Finland" },
|
||||
|
||||
@ -37,6 +37,7 @@ export class AccountsService {
|
||||
password: string,
|
||||
username: string,
|
||||
role: string,
|
||||
authorId: string,
|
||||
acceptedEulaVersion: string,
|
||||
acceptedPrivacyNoticeVersion: string,
|
||||
acceptedDpaVersion: string,
|
||||
@ -78,6 +79,7 @@ export class AccountsService {
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
}
|
||||
this.logger.log("idpにユーザーを作成成功");
|
||||
|
||||
// メールアドレス重複エラー
|
||||
if (isConflictError(externalUser)) {
|
||||
@ -89,6 +91,7 @@ export class AccountsService {
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
this.logger.log("メールアドレスは重複していません");
|
||||
|
||||
let account: Account;
|
||||
let user: User;
|
||||
@ -103,6 +106,7 @@ export class AccountsService {
|
||||
type,
|
||||
externalUser.sub,
|
||||
role,
|
||||
authorId,
|
||||
accountId,
|
||||
userId,
|
||||
acceptedEulaVersion,
|
||||
@ -136,6 +140,7 @@ export class AccountsService {
|
||||
account.id,
|
||||
country
|
||||
);
|
||||
this.logger.log("コンテナー作成成功");
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
this.logger.error(
|
||||
|
||||
@ -11,6 +11,7 @@ import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
|
||||
import { Request } from "express";
|
||||
import { DeleteService } from "./delete.service";
|
||||
import { DeleteResponse } from "./types/types";
|
||||
import { makeContext } from "src/common/log";
|
||||
|
||||
@ApiTags("delete")
|
||||
@Controller("delete")
|
||||
@ -33,7 +34,9 @@ export class DeleteController {
|
||||
})
|
||||
@Post()
|
||||
async deleteData(): Promise<{}> {
|
||||
await this.deleteService.deleteData();
|
||||
const context = makeContext("tool", "delete");
|
||||
|
||||
await this.deleteService.deleteData(context);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import { DeleteRepositoryService } from "../../repositories/delete/delete.reposi
|
||||
import { makeErrorResponse } from "../../common/errors/makeErrorResponse";
|
||||
import { AdB2cService } from "../../gateways/adb2c/adb2c.service";
|
||||
import { BlobstorageService } from "../../gateways/blobstorage/blobstorage.service";
|
||||
import { Context } from "../../common/log";
|
||||
|
||||
@Injectable()
|
||||
export class DeleteService {
|
||||
@ -11,27 +12,41 @@ export class DeleteService {
|
||||
private readonly deleteRepositoryService: DeleteRepositoryService,
|
||||
private readonly blobstorageService: BlobstorageService,
|
||||
private readonly adB2cService: AdB2cService
|
||||
) {}
|
||||
) { }
|
||||
|
||||
/**
|
||||
* データを削除する
|
||||
* @returns data
|
||||
*/
|
||||
async deleteData(): Promise<void> {
|
||||
this.logger.log(`[IN] ${this.deleteData.name}`);
|
||||
async deleteData(context: Context): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${this.deleteData.name}`
|
||||
);
|
||||
try {
|
||||
// BlobStorageからデータを削除する
|
||||
await this.blobstorageService.deleteContainers();
|
||||
await this.blobstorageService.deleteContainers(context);
|
||||
|
||||
// ADB2Cからユーザ情報を取得する
|
||||
const users = await this.adB2cService.getUsers();
|
||||
const externalIds = users.map((user) => user.id);
|
||||
await this.adB2cService.deleteUsers(externalIds);
|
||||
// 100件ずつのユーザー取得なのですべて削除するまでループする
|
||||
for (let i = 0; i < 500; i++) {
|
||||
// ADB2Cからユーザ情報を取得する
|
||||
const { users, hasNext } = await this.adB2cService.getUsers(context);
|
||||
|
||||
|
||||
const externalIds = users.map((user) => user.id);
|
||||
await this.adB2cService.deleteUsers(context, externalIds);
|
||||
|
||||
// 削除していないユーザーがいない場合はループを抜ける
|
||||
if (!hasNext) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// データベースからデータを削除する
|
||||
await this.deleteRepositoryService.deleteData();
|
||||
// AutoIncrementの値をリセットする
|
||||
await this.deleteRepositoryService.resetAutoIncrement();
|
||||
// 初期データを挿入する
|
||||
await this.deleteRepositoryService.insertInitData(context);
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
if (e instanceof Error) {
|
||||
|
||||
@ -17,11 +17,11 @@ import { AccountsService } from "../accounts/accounts.service";
|
||||
import { UsersService } from "../users/users.service";
|
||||
import { makeContext } from "../../common/log";
|
||||
import {
|
||||
isAccountsInputFileArray,
|
||||
isUsersInputFileArray,
|
||||
isLicensesInputFileArray,
|
||||
isWorktypesInputFileArray,
|
||||
isCardLicensesInputFileArray,
|
||||
isAccountsFileArray,
|
||||
isUsersFileArray,
|
||||
isLicensesFileArray,
|
||||
isWorktypesFileArray,
|
||||
isCardLicensesFileArray,
|
||||
} from "../../common/types/types";
|
||||
import { makePassword } from "../../common/password/password";
|
||||
import {
|
||||
@ -73,13 +73,24 @@ export class RegisterController {
|
||||
const cardLicensesFileFullPath = inputFilePath + "cardLicenses.json";
|
||||
|
||||
// ファイル存在チェックと読み込み
|
||||
if (
|
||||
!fs.existsSync(accouncsFileFullPath) ||
|
||||
!fs.existsSync(usersFileFullPath) ||
|
||||
!fs.existsSync(licensesFileFullPath) ||
|
||||
!fs.existsSync(worktypesFileFullPath) ||
|
||||
!fs.existsSync(cardLicensesFileFullPath)
|
||||
) {
|
||||
// どのファイルがないのかわからないのでそれぞれに存在しない場合はエラーを出す
|
||||
if (!fs.existsSync(accouncsFileFullPath)) {
|
||||
this.logger.error(`file not exists from ${inputFilePath}`);
|
||||
throw new Error(`file not exists from ${inputFilePath}`);
|
||||
}
|
||||
if (!fs.existsSync(usersFileFullPath)) {
|
||||
this.logger.error(`file not exists from ${inputFilePath}`);
|
||||
throw new Error(`file not exists from ${inputFilePath}`);
|
||||
}
|
||||
if (!fs.existsSync(licensesFileFullPath)) {
|
||||
this.logger.error(`file not exists from ${inputFilePath}`);
|
||||
throw new Error(`file not exists from ${inputFilePath}`);
|
||||
}
|
||||
if (!fs.existsSync(worktypesFileFullPath)) {
|
||||
this.logger.error(`file not exists from ${inputFilePath}`);
|
||||
throw new Error(`file not exists from ${inputFilePath}`);
|
||||
}
|
||||
if (!fs.existsSync(cardLicensesFileFullPath)) {
|
||||
this.logger.error(`file not exists from ${inputFilePath}`);
|
||||
throw new Error(`file not exists from ${inputFilePath}`);
|
||||
}
|
||||
@ -90,34 +101,53 @@ export class RegisterController {
|
||||
);
|
||||
|
||||
// 型ガード(account)
|
||||
if (!isAccountsInputFileArray(accountsObject)) {
|
||||
throw new Error("input file is not accountsInputFiles");
|
||||
if (!isAccountsFileArray(accountsObject)) {
|
||||
throw new Error("input file is not AccountsFiles");
|
||||
}
|
||||
|
||||
for (const accountsInputFile of accountsObject) {
|
||||
for (const AccountsFile of accountsObject) {
|
||||
this.logger.log("ランダムパスワード生成開始");
|
||||
// ランダムなパスワードを生成する
|
||||
const ramdomPassword = makePassword();
|
||||
this.logger.log("ランダムパスワード生成完了");
|
||||
// roleの設定
|
||||
// roleの値がnullなら"none"、null以外ならroleの値、
|
||||
// また、roleの値が"author"なら"author"を設定
|
||||
let role: string;
|
||||
let authorId: string;
|
||||
if (AccountsFile.role === null) {
|
||||
role = USER_ROLES.NONE;
|
||||
authorId = null;
|
||||
} else if (AccountsFile.role === USER_ROLES.AUTHOR) {
|
||||
role = USER_ROLES.AUTHOR;
|
||||
authorId = AccountsFile.authorId;
|
||||
} else {
|
||||
// ありえないが、roleの値が"none"または"author"の文字列以外の場合はエラーを返す
|
||||
throw new Error("Invalid role value");
|
||||
}
|
||||
this.logger.log("account生成開始");
|
||||
await this.accountsService.createAccount(
|
||||
context,
|
||||
accountsInputFile.companyName,
|
||||
accountsInputFile.country,
|
||||
accountsInputFile.dealerAccountId,
|
||||
accountsInputFile.adminMail,
|
||||
AccountsFile.companyName,
|
||||
AccountsFile.country,
|
||||
AccountsFile.dealerAccountId,
|
||||
AccountsFile.adminMail,
|
||||
ramdomPassword,
|
||||
accountsInputFile.adminName,
|
||||
"none",
|
||||
AccountsFile.adminName,
|
||||
role,
|
||||
authorId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
accountsInputFile.type,
|
||||
accountsInputFile.accountId,
|
||||
accountsInputFile.userId
|
||||
AccountsFile.type,
|
||||
AccountsFile.accountId,
|
||||
AccountsFile.userId
|
||||
);
|
||||
|
||||
// ratelimit対応のためsleepを行う
|
||||
await sleep(MIGRATION_DATA_REGISTER_INTERVAL_MILLISEC);
|
||||
}
|
||||
// const accountsInputFiles = accountsObject as AccountsInputFile[];
|
||||
// const AccountsFiles = accountsObject as AccountsFile[];
|
||||
|
||||
// ユーザの登録用ファイル読み込み
|
||||
const usersObject = JSON.parse(
|
||||
@ -125,24 +155,24 @@ export class RegisterController {
|
||||
);
|
||||
|
||||
// 型ガード(user)
|
||||
if (!isUsersInputFileArray(usersObject)) {
|
||||
throw new Error("input file is not usersInputFiles");
|
||||
if (!isUsersFileArray(usersObject)) {
|
||||
throw new Error("input file is not UsersFiles");
|
||||
}
|
||||
|
||||
for (const usersInputFile of usersObject) {
|
||||
this.logger.log(usersInputFile.name);
|
||||
for (const UsersFile of usersObject) {
|
||||
this.logger.log(UsersFile.name);
|
||||
await this.usersService.createUser(
|
||||
context,
|
||||
usersInputFile.name,
|
||||
usersInputFile.role === USER_ROLES.AUTHOR
|
||||
UsersFile.name,
|
||||
UsersFile.role === USER_ROLES.AUTHOR
|
||||
? USER_ROLES.AUTHOR
|
||||
: USER_ROLES.NONE,
|
||||
usersInputFile.email,
|
||||
UsersFile.email,
|
||||
true,
|
||||
true,
|
||||
usersInputFile.accountId,
|
||||
usersInputFile.userId,
|
||||
usersInputFile.authorId,
|
||||
UsersFile.accountId,
|
||||
UsersFile.userId,
|
||||
UsersFile.authorId,
|
||||
false,
|
||||
null,
|
||||
true
|
||||
@ -157,8 +187,8 @@ export class RegisterController {
|
||||
);
|
||||
|
||||
// 型ガード(license)
|
||||
if (!isLicensesInputFileArray(licensesObject)) {
|
||||
throw new Error("input file is not licensesInputFiles");
|
||||
if (!isLicensesFileArray(licensesObject)) {
|
||||
throw new Error("input file is not LicensesFiles");
|
||||
}
|
||||
|
||||
// ワークタイプの登録用ファイル読み込み
|
||||
@ -167,8 +197,8 @@ export class RegisterController {
|
||||
);
|
||||
|
||||
// 型ガード(Worktypes)
|
||||
if (!isWorktypesInputFileArray(worktypesObject)) {
|
||||
throw new Error("input file is not WorktypesInputFiles");
|
||||
if (!isWorktypesFileArray(worktypesObject)) {
|
||||
throw new Error("input file is not WorktypesFiles");
|
||||
}
|
||||
|
||||
// カードライセンスの登録用ファイル読み込み
|
||||
@ -177,8 +207,8 @@ export class RegisterController {
|
||||
);
|
||||
|
||||
// 型ガード(cardLicenses)
|
||||
if (!isCardLicensesInputFileArray(cardLicensesObject)) {
|
||||
throw new Error("input file is not cardLicensesInputFiles");
|
||||
if (!isCardLicensesFileArray(cardLicensesObject)) {
|
||||
throw new Error("input file is not cardLicensesFiles");
|
||||
}
|
||||
|
||||
// ライセンス・ワークタイプ・カードライセンスの登録
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common";
|
||||
import { Context } from "../../common/log";
|
||||
import {
|
||||
LicensesInputFile,
|
||||
WorktypesInputFile,
|
||||
CardLicensesInputFile,
|
||||
LicensesFile,
|
||||
WorktypesFile,
|
||||
CardLicensesFile,
|
||||
} from "../../common/types/types";
|
||||
import { LicensesRepositoryService } from "../../repositories/licenses/licenses.repository.service";
|
||||
import { WorktypesRepositoryService } from "../../repositories/worktypes/worktypes.repository.service";
|
||||
@ -22,9 +22,9 @@ export class RegisterService {
|
||||
*/
|
||||
async registLicenseAndWorktypeData(
|
||||
context: Context,
|
||||
licensesInputFiles: LicensesInputFile[],
|
||||
worktypesInputFiles: WorktypesInputFile[],
|
||||
cardlicensesInputFiles: CardLicensesInputFile[]
|
||||
LicensesFiles: LicensesFile[],
|
||||
WorktypesFiles: WorktypesFile[],
|
||||
cardLicensesFiles: CardLicensesFile[]
|
||||
): Promise<void> {
|
||||
// パラメータ内容が長大なのでログには出さない
|
||||
this.logger.log(
|
||||
@ -35,20 +35,17 @@ export class RegisterService {
|
||||
|
||||
try {
|
||||
this.logger.log("Licenses register start");
|
||||
await this.licensesRepository.insertLicenses(context, licensesInputFiles);
|
||||
await this.licensesRepository.insertLicenses(context, LicensesFiles);
|
||||
this.logger.log("Licenses register end");
|
||||
|
||||
this.logger.log("Worktypes register start");
|
||||
await this.worktypesRepository.createWorktype(
|
||||
context,
|
||||
worktypesInputFiles
|
||||
);
|
||||
await this.worktypesRepository.createWorktype(context, WorktypesFiles);
|
||||
this.logger.log("Worktypes register end");
|
||||
|
||||
this.logger.log("CardLicenses register start");
|
||||
await this.licensesRepository.insertCardLicenses(
|
||||
context,
|
||||
cardlicensesInputFiles
|
||||
cardLicensesFiles
|
||||
);
|
||||
this.logger.log("CardLicenses register end");
|
||||
} catch (e) {
|
||||
|
||||
@ -11,25 +11,16 @@ import fs from "fs";
|
||||
import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
|
||||
import { Request } from "express";
|
||||
import { transferRequest, transferResponse } from "./types/types";
|
||||
import { transferService } from "./transfer.service";
|
||||
import { TransferService } from "./transfer.service";
|
||||
import { makeContext } from "../../common/log";
|
||||
import { csvInputFile } from "../../common/types/types";
|
||||
import { csvInputFile, AccountsMappingFile } from "../../common/types/types";
|
||||
import { makeErrorResponse } from "src/common/errors/makeErrorResponse";
|
||||
import {
|
||||
COUNTRY_LIST,
|
||||
MIGRATION_TYPE,
|
||||
TIERS,
|
||||
WORKTYPE_MAX_COUNT,
|
||||
RECORDING_MODE,
|
||||
LICENSE_ALLOCATED_STATUS,
|
||||
USER_ROLES,
|
||||
AUTO_INCREMENT_START,
|
||||
} from "../../../src/constants";
|
||||
import { AUTO_INCREMENT_START } from "../../constants";
|
||||
@ApiTags("transfer")
|
||||
@Controller("transfer")
|
||||
export class transferController {
|
||||
private readonly logger = new Logger(transferController.name);
|
||||
constructor(private readonly transferService: transferService) {}
|
||||
export class TransferController {
|
||||
private readonly logger = new Logger(TransferController.name);
|
||||
constructor(private readonly transferService: TransferService) {}
|
||||
|
||||
@Post()
|
||||
@ApiResponse({
|
||||
@ -57,16 +48,16 @@ export class transferController {
|
||||
);
|
||||
try {
|
||||
// 読み込みファイルのフルパス
|
||||
const csvFileFullPath = inputFilePath + ".csv";
|
||||
const accouncsFileFullPath = inputFilePath + "Account_transition.csv";
|
||||
|
||||
// ファイル存在チェックと読み込み
|
||||
if (!fs.existsSync(csvFileFullPath)) {
|
||||
if (!fs.existsSync(accouncsFileFullPath)) {
|
||||
this.logger.error(`file not exists from ${inputFilePath}`);
|
||||
throw new Error(`file not exists from ${inputFilePath}`);
|
||||
}
|
||||
|
||||
// CSVファイルを全行読み込む
|
||||
const inputFile = fs.readFileSync(csvFileFullPath, "utf-8");
|
||||
const inputFile = fs.readFileSync(accouncsFileFullPath, "utf-8");
|
||||
|
||||
// レコードごとに分割
|
||||
const csvInputFileLines = inputFile.split("\n");
|
||||
@ -77,45 +68,78 @@ export class transferController {
|
||||
// 項目ごとに切り分ける
|
||||
let csvInputFile: csvInputFile[] = [];
|
||||
csvInputFileLines.forEach((line) => {
|
||||
// 項目にカンマが入っている場合を考慮して、ダブルクォーテーションで囲まれた部分を一つの項目として扱う
|
||||
const regExp = /"[^"]*"/g;
|
||||
const matchList = line.match(regExp);
|
||||
if (matchList) {
|
||||
matchList.forEach((match) => {
|
||||
// カンマを\に変換
|
||||
const replaced = match.replace(/,/g, "\\");
|
||||
line = line.replace(match, replaced);
|
||||
});
|
||||
}
|
||||
const data = line.split(",");
|
||||
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: new Date(data[9]),
|
||||
expired_date: new 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],
|
||||
// ダブルクォーテーションを削除
|
||||
data.forEach((value, index) => {
|
||||
data[index] = value.replace(/"/g, "");
|
||||
});
|
||||
// "\r"を削除
|
||||
data[data.length - 1] = data[data.length - 1].replace(/\r/g, "");
|
||||
// dataの要素数が34(csvInputFileの要素数)より多い場合、フォーマット不一致エラー(移行元はworktypeの数が20より多く設定できるので理論上は存在する)
|
||||
// worktypeの数の確認を促すエラーを出す
|
||||
if (data.length > 34) {
|
||||
this.logger.error(
|
||||
`[${context.getTrackingId()}] format error.please check the number of elements in worktype. data=${data}`
|
||||
);
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E009999"),
|
||||
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],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 各データのバリデーションチェック
|
||||
await this.transferService.validateInputData(context, csvInputFile);
|
||||
|
||||
@ -127,36 +151,63 @@ 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 transferResponse = await this.transferService.registInputData(
|
||||
const transferResponseCsv = await this.transferService.transferInputData(
|
||||
context,
|
||||
csvInputFile,
|
||||
accountIdMap
|
||||
);
|
||||
|
||||
// countryを除いた階層の再配置
|
||||
const accountsOutputFileStep1Lines =
|
||||
transferResponse.accountsOutputFileStep1Lines;
|
||||
const accountsOutputFile = await this.transferService.relocateHierarchy(
|
||||
const AccountsFileTypeLines = transferResponseCsv.accountsFileTypeLines;
|
||||
const AccountsFile = await this.transferService.relocateHierarchy(
|
||||
context,
|
||||
accountsOutputFileStep1Lines
|
||||
AccountsFileTypeLines
|
||||
);
|
||||
const UsersFile = transferResponseCsv.usersFileLines;
|
||||
const LicensesFile = transferResponseCsv.licensesFileLines;
|
||||
// メールアドレスの重複を削除
|
||||
// デモライセンスの削除
|
||||
// いったんこのままコミットしてテストを実施する
|
||||
const resultDuplicateEmail =
|
||||
await this.transferService.removeDuplicateEmail(
|
||||
context,
|
||||
AccountsFile,
|
||||
UsersFile,
|
||||
LicensesFile
|
||||
);
|
||||
|
||||
// transferResponseを4つのJSONファイルの出力する(出力先はinputと同じにする)
|
||||
// AuthorIDが重複している場合通番を付与する
|
||||
const transferDuplicateAuthorResultUsers =
|
||||
await this.transferService.transferDuplicateAuthor(
|
||||
context,
|
||||
resultDuplicateEmail.accountsFileLines,
|
||||
resultDuplicateEmail.usersFileLines
|
||||
);
|
||||
|
||||
// transferResponseCsvを4つのJSONファイルの出力する(出力先はinputと同じにする)
|
||||
const outputFilePath = body.inputFilePath;
|
||||
const usersOutputFile = transferResponse.usersOutputFileLines;
|
||||
const licensesOutputFile = transferResponse.licensesOutputFileLines;
|
||||
const worktypesOutputFile = transferResponse.worktypesOutputFileLines;
|
||||
const WorktypesFile = transferResponseCsv.worktypesFileLines;
|
||||
this.transferService.outputJsonFile(
|
||||
context,
|
||||
outputFilePath,
|
||||
accountsOutputFile,
|
||||
usersOutputFile,
|
||||
licensesOutputFile,
|
||||
worktypesOutputFile
|
||||
resultDuplicateEmail.accountsFileLines,
|
||||
transferDuplicateAuthorResultUsers,
|
||||
resultDuplicateEmail.licensesFileLines,
|
||||
WorktypesFile
|
||||
);
|
||||
return {};
|
||||
} catch (e) {
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { transferController } from "./transfer.controller";
|
||||
import { transferService } from "./transfer.service";
|
||||
import { TransferController } from "./transfer.controller";
|
||||
import { TransferService } from "./transfer.service";
|
||||
@Module({
|
||||
imports: [],
|
||||
controllers: [transferController],
|
||||
providers: [transferService],
|
||||
controllers: [TransferController],
|
||||
providers: [TransferService],
|
||||
})
|
||||
export class transferModule {}
|
||||
export class TransferModule {}
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common";
|
||||
import { Context } from "../../common/log";
|
||||
import {
|
||||
AccountsOutputFileStep1,
|
||||
UsersOutputFile,
|
||||
LicensesOutputFile,
|
||||
WorktypesOutputFile,
|
||||
AccountsFileType,
|
||||
UsersFile,
|
||||
LicensesFile,
|
||||
WorktypesFile,
|
||||
csvInputFile,
|
||||
AccountsOutputFile,
|
||||
AccountsFile,
|
||||
} from "../../common/types/types";
|
||||
import {
|
||||
COUNTRY_LIST,
|
||||
@ -18,49 +18,64 @@ import {
|
||||
USER_ROLES,
|
||||
SWITCH_FROM_TYPE,
|
||||
} from "src/constants";
|
||||
import { registInputDataResponse } from "./types/types";
|
||||
import {
|
||||
registInputDataResponse,
|
||||
removeDuplicateEmailResponse,
|
||||
} from "./types/types";
|
||||
import fs from "fs";
|
||||
import { makeErrorResponse } from "src/common/error/makeErrorResponse";
|
||||
|
||||
@Injectable()
|
||||
export class transferService {
|
||||
export class TransferService {
|
||||
constructor() {}
|
||||
private readonly logger = new Logger(transferService.name);
|
||||
private readonly logger = new Logger(TransferService.name);
|
||||
|
||||
/**
|
||||
* Regist Data
|
||||
* Transfer Input Data
|
||||
* @param OutputFilePath: string
|
||||
* @param csvInputFile: csvInputFile[]
|
||||
*/
|
||||
async registInputData(
|
||||
async transferInputData(
|
||||
context: Context,
|
||||
csvInputFile: csvInputFile[],
|
||||
accountIdMap: Map<string, number>
|
||||
): Promise<registInputDataResponse> {
|
||||
// パラメータ内容が長大なのでログには出さない
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${this.registInputData.name}`
|
||||
`[IN] [${context.getTrackingId()}] ${this.transferInputData.name}`
|
||||
);
|
||||
|
||||
try {
|
||||
let accountsOutputFileStep1Lines: AccountsOutputFileStep1[] = [];
|
||||
let usersOutputFileLines: UsersOutputFile[] = [];
|
||||
let licensesOutputFileLines: LicensesOutputFile[] = [];
|
||||
let worktypesOutputFileLines: WorktypesOutputFile[] = [];
|
||||
|
||||
let accountsFileTypeLines: AccountsFileType[] = [];
|
||||
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"以外の場合、アカウントデータの作成を行う
|
||||
if (line.type !== MIGRATION_TYPE.USER) {
|
||||
// userIdのインクリメント
|
||||
userIdIndex = userIdIndex + 1;
|
||||
// line.countryの値を読み込みCOUNTRY_LISTのlabelからvalueに変換する
|
||||
const country = COUNTRY_LIST.find(
|
||||
(country) => country.label === line.country
|
||||
)?.value;
|
||||
// adminNameの変換(last_name + " "+ first_name)
|
||||
const adminName = `${line.last_name} ${line.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();
|
||||
}
|
||||
// ランダムパスワードの生成(データ登録ツール側で行うのでやらない)
|
||||
// common/password/password.tsのmakePasswordを使用
|
||||
// const autoGeneratedPassword = makePassword();
|
||||
@ -71,8 +86,18 @@ export class transferService {
|
||||
if (line.parent_id) {
|
||||
parentAccountId = accountIdMap.get(line.parent_id);
|
||||
}
|
||||
// AccountsOutputFile配列にPush
|
||||
accountsOutputFileStep1Lines.push({
|
||||
// 万が一parent_idが入力されているのに存在しなかった場合は、エラー配列に追加する
|
||||
if (parentAccountId === undefined) {
|
||||
errorArray.push(
|
||||
`parent_id is invalid. parent_id=${line.parent_id}`
|
||||
);
|
||||
}
|
||||
|
||||
// userIdIndexをインクリメントする
|
||||
userIdIndex++;
|
||||
|
||||
// AccountsFile配列にPush
|
||||
accountsFileTypeLines.push({
|
||||
// accountIdはaccountIdMapから取得する
|
||||
accountId: accountIdMap.get(line.account_id),
|
||||
type: line.type,
|
||||
@ -82,148 +107,180 @@ export class transferService {
|
||||
adminName: adminName,
|
||||
adminMail: line.email,
|
||||
userId: userIdIndex,
|
||||
role: null,
|
||||
authorId: null,
|
||||
});
|
||||
} else {
|
||||
// typeが"USER"の場合、ユーザデータの作成を行う
|
||||
// userIdのインクリメント
|
||||
userIdIndex = userIdIndex + 1;
|
||||
// nameの変換
|
||||
// もしline.last_nameとline.first_nameが存在しない場合、line.emailをnameにする
|
||||
// 存在する場合は、last_name + " " + first_name
|
||||
let name = line.email;
|
||||
if (line.last_name && line.first_name) {
|
||||
name = `${line.last_name} ${line.first_name}`;
|
||||
}
|
||||
// roleの変換
|
||||
// authorIdが設定されてる場合はauthor、されていない場合は移行しないので次の行に進む
|
||||
if (line.author_id) {
|
||||
usersOutputFileLines.push({
|
||||
accountId: accountIdMap.get(line.account_id),
|
||||
userId: userIdIndex,
|
||||
name: name,
|
||||
role: USER_ROLES.AUTHOR,
|
||||
authorId: line.author_id,
|
||||
email: line.user_email,
|
||||
});
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
// ライセンスのデータの作成を行う
|
||||
// authorIdが設定されてる場合、statusは"allocated"、allocated_user_idは対象のユーザID
|
||||
// されていない場合、statusは"reusable"、allocated_user_idはnull
|
||||
licensesOutputFileLines.push({
|
||||
expiry_date: line.expired_date.toISOString(),
|
||||
account_id: accountIdMap.get(line.account_id),
|
||||
type: SWITCH_FROM_TYPE.NONE,
|
||||
status: line.author_id
|
||||
? LICENSE_ALLOCATED_STATUS.ALLOCATED
|
||||
: LICENSE_ALLOCATED_STATUS.REUSABLE,
|
||||
allocated_user_id: line.author_id ? userIdIndex : null,
|
||||
});
|
||||
// WorktypesOutputFileの作成
|
||||
// wt1~wt20まで読み込み、account単位で作成する
|
||||
// 作成したWorktypesOutputFileを配列にPush
|
||||
for (let i = 1; i <= WORKTYPE_MAX_COUNT; i++) {
|
||||
const wt = `wt${i}`;
|
||||
if (line[wt]) {
|
||||
// 既に存在する場合は、作成しない
|
||||
if (
|
||||
worktypesOutputFileLines.find(
|
||||
(worktype) =>
|
||||
worktype.account_id === accountIdMap.get(line.account_id) &&
|
||||
worktype.custom_worktype_id === line[wt].toString()
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
// typeが"USER"の場合、かつcountryのアカウントIDに所属していない場合
|
||||
if (
|
||||
line.type == MIGRATION_TYPE.USER &&
|
||||
!countryAccounts.some(
|
||||
(countryAccount) => countryAccount.account_id === line.account_id
|
||||
)
|
||||
) {
|
||||
// line.author_idが存在する場合のみユーザーデータを作成する
|
||||
if (line.author_id) {
|
||||
// userIdIndexをインクリメントする
|
||||
userIdIndex++;
|
||||
|
||||
// nameの変換
|
||||
// もしline.last_nameとline.first_nameが存在しない場合、line.emailをnameにする
|
||||
// 存在する場合は、last_name + " " + first_name
|
||||
let name = line.user_email;
|
||||
if (line.last_name && line.first_name) {
|
||||
name = `${line.last_name} ${line.first_name}`;
|
||||
}
|
||||
// UsersFileの作成
|
||||
usersFileLines.push({
|
||||
accountId: accountIdMap.get(line.account_id),
|
||||
userId: userIdIndex,
|
||||
name: name,
|
||||
role: USER_ROLES.AUTHOR,
|
||||
authorId: line.author_id,
|
||||
email: line.user_email,
|
||||
});
|
||||
// authorIdとuserIdの対応関係をマッピング
|
||||
authorIdToUserIdMap.set(line.author_id, userIdIndex);
|
||||
}
|
||||
|
||||
// ライセンスのデータの作成を行う
|
||||
// line.expired_dateが"9999/12/31"で始まるデータの場合はデモライセンスなので登録しない
|
||||
if (!line.expired_date.startsWith("9999/12/31")) {
|
||||
// authorIdが設定されてる場合、statusは"allocated"、allocated_user_idは対象のユーザID
|
||||
// されていない場合、statusは"reusable"、allocated_user_idはnull
|
||||
let status: string;
|
||||
let allocated_user_id: number | null;
|
||||
if (line.author_id) {
|
||||
status = LICENSE_ALLOCATED_STATUS.ALLOCATED;
|
||||
allocated_user_id =
|
||||
authorIdToUserIdMap.get(line.author_id) ?? null; // authorIdに対応するuserIdを取得
|
||||
} else {
|
||||
status = LICENSE_ALLOCATED_STATUS.REUSABLE;
|
||||
allocated_user_id = null;
|
||||
}
|
||||
// LicensesFileの作成
|
||||
licensesFileLines.push({
|
||||
expiry_date: line.expired_date,
|
||||
account_id: accountIdMap.get(line.account_id),
|
||||
type: SWITCH_FROM_TYPE.NONE,
|
||||
status: status,
|
||||
allocated_user_id: allocated_user_id,
|
||||
});
|
||||
}
|
||||
// WorktypesFileの作成
|
||||
// wt1~wt20まで読み込み、account単位で作成する
|
||||
// 作成したWorktypesFileを配列にPush
|
||||
for (let i = 1; i <= WORKTYPE_MAX_COUNT; i++) {
|
||||
const wt = `wt${i}`;
|
||||
if (line[wt]) {
|
||||
// account_idで同一のcustom_worktype_idが存在しない場合は、作成する
|
||||
if (
|
||||
!worktypesFileLines.find(
|
||||
(worktype) =>
|
||||
worktype.account_id ===
|
||||
accountIdMap.get(line.account_id) &&
|
||||
worktype.custom_worktype_id === line[wt]
|
||||
)
|
||||
) {
|
||||
worktypesFileLines.push({
|
||||
account_id: accountIdMap.get(line.account_id),
|
||||
custom_worktype_id: line[wt],
|
||||
});
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// つぎの行に進む
|
||||
});
|
||||
// エラー配列に値が存在する場合はエラーファイルを出力する
|
||||
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 {
|
||||
accountsOutputFileStep1Lines,
|
||||
usersOutputFileLines,
|
||||
licensesOutputFileLines,
|
||||
worktypesOutputFileLines,
|
||||
accountsFileTypeLines,
|
||||
usersFileLines,
|
||||
licensesFileLines,
|
||||
worktypesFileLines,
|
||||
};
|
||||
} 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.registInputData.name}`
|
||||
`[OUT] [${context.getTrackingId()}] ${this.transferInputData.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 階層の付け替えを行う
|
||||
* @param accountsOutputFileStep1: AccountsOutputFileStep1[]
|
||||
* @returns AccountsOutputFile[]
|
||||
* @param accountsFileType: AccountsFileType[]
|
||||
* @returns AccountsFile[]
|
||||
*/
|
||||
async relocateHierarchy(
|
||||
context: Context,
|
||||
accountsOutputFileStep1: AccountsOutputFileStep1[]
|
||||
): Promise<AccountsOutputFile[]> {
|
||||
accountsFileType: AccountsFileType[]
|
||||
): Promise<AccountsFile[]> {
|
||||
// パラメータ内容が長大なのでログには出さない
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${this.relocateHierarchy.name}`
|
||||
);
|
||||
|
||||
try {
|
||||
// dealerAccountIdを検索し、typeがCountryの場合
|
||||
accountsOutputFileStep1.forEach((account) => {
|
||||
if (account.type === MIGRATION_TYPE.COUNTRY) {
|
||||
// そのacccountIdをdealerAccountIdにもつアカウント(Distributor)を検索する
|
||||
const distributor = accountsOutputFileStep1.find(
|
||||
(distributor) =>
|
||||
account.type === MIGRATION_TYPE.DISTRIBUTOR &&
|
||||
distributor.dealerAccountId === account.accountId
|
||||
);
|
||||
// DistributorのdealerAccountIdをBC(Countryの親)に付け替える
|
||||
distributor.dealerAccountId = account.dealerAccountId;
|
||||
}
|
||||
});
|
||||
// typeがCountryのアカウントを取り除く
|
||||
accountsOutputFileStep1 = accountsOutputFileStep1.filter(
|
||||
(account) => account.type !== MIGRATION_TYPE.COUNTRY
|
||||
const relocatedAccounts: AccountsFile[] = [];
|
||||
const dealerRecords: Map<number, number> = new Map();
|
||||
|
||||
const countryAccounts = accountsFileType.filter(
|
||||
(item) => item.type === MIGRATION_TYPE.COUNTRY
|
||||
);
|
||||
|
||||
// typeをtierに変換し、AccountsOutputFileに変換する
|
||||
let accountsOutputFile: AccountsOutputFile[] = [];
|
||||
accountsOutputFileStep1.forEach((account) => {
|
||||
let tier = 0;
|
||||
switch (account.type) {
|
||||
case MIGRATION_TYPE.ADMINISTRATOR:
|
||||
tier = TIERS.TIER1;
|
||||
break;
|
||||
case MIGRATION_TYPE.BC:
|
||||
tier = TIERS.TIER2;
|
||||
break;
|
||||
case MIGRATION_TYPE.DISTRIBUTOR:
|
||||
tier = TIERS.TIER3;
|
||||
break;
|
||||
case MIGRATION_TYPE.DEALER:
|
||||
tier = TIERS.TIER4;
|
||||
break;
|
||||
case MIGRATION_TYPE.CUSTOMER:
|
||||
tier = TIERS.TIER5;
|
||||
break;
|
||||
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;
|
||||
}
|
||||
}
|
||||
accountsOutputFile.push({
|
||||
accountId: account.accountId,
|
||||
type: tier,
|
||||
companyName: account.companyName,
|
||||
country: account.country,
|
||||
dealerAccountId: account.dealerAccountId,
|
||||
adminName: account.adminName,
|
||||
adminMail: account.adminMail,
|
||||
userId: account.userId,
|
||||
});
|
||||
|
||||
const assignType = this.getAccountType(notCountryAccount.type);
|
||||
|
||||
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);
|
||||
});
|
||||
return accountsOutputFile;
|
||||
|
||||
return relocatedAccounts;
|
||||
} 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.relocateHierarchy.name}`
|
||||
@ -231,21 +288,38 @@ export class transferService {
|
||||
}
|
||||
}
|
||||
|
||||
// メソッド: アカウントタイプを数値に変換するヘルパー関数
|
||||
private getAccountType(type: string): number {
|
||||
switch (type) {
|
||||
case MIGRATION_TYPE.ADMINISTRATOR:
|
||||
return TIERS.TIER1;
|
||||
case MIGRATION_TYPE.BC:
|
||||
return TIERS.TIER2;
|
||||
case MIGRATION_TYPE.DISTRIBUTOR:
|
||||
return TIERS.TIER3;
|
||||
case MIGRATION_TYPE.DEALER:
|
||||
return TIERS.TIER4;
|
||||
case MIGRATION_TYPE.CUSTOMER:
|
||||
return TIERS.TIER5;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* JSONファイルの出力
|
||||
* @param outputFilePath: string
|
||||
* @param accountsOutputFile: AccountsOutputFile[]
|
||||
* @param usersOutputFile: UsersOutputFile[]
|
||||
* @param licensesOutputFile: LicensesOutputFile[]
|
||||
* @param worktypesOutputFile: WorktypesOutputFile[]
|
||||
* @param accountsFile: AccountsFile[]
|
||||
* @param usersFile: UsersFile[]
|
||||
* @param licensesFile: LicensesFile[]
|
||||
* @param worktypesFile: WorktypesFile[]
|
||||
*/
|
||||
async outputJsonFile(
|
||||
context: Context,
|
||||
outputFilePath: string,
|
||||
accountsOutputFile: AccountsOutputFile[],
|
||||
usersOutputFile: UsersOutputFile[],
|
||||
licensesOutputFile: LicensesOutputFile[],
|
||||
worktypesOutputFile: WorktypesOutputFile[]
|
||||
accountsFile: AccountsFile[],
|
||||
usersFile: UsersFile[],
|
||||
licensesFile: LicensesFile[],
|
||||
worktypesFile: WorktypesFile[]
|
||||
): Promise<void> {
|
||||
// パラメータ内容が長大なのでログには出さない
|
||||
this.logger.log(
|
||||
@ -254,29 +328,24 @@ export class transferService {
|
||||
|
||||
try {
|
||||
// JSONファイルの出力を行う
|
||||
// accountsOutputFile配列の出力
|
||||
const accountsOutputFileJson = JSON.stringify(accountsOutputFile);
|
||||
fs.writeFileSync(
|
||||
`${outputFilePath}_accounts.json`,
|
||||
accountsOutputFileJson
|
||||
);
|
||||
// usersOutputFile
|
||||
const usersOutputFileJson = JSON.stringify(usersOutputFile);
|
||||
fs.writeFileSync(`${outputFilePath}_users.json`, usersOutputFileJson);
|
||||
// licensesOutputFile
|
||||
const licensesOutputFileJson = JSON.stringify(licensesOutputFile);
|
||||
fs.writeFileSync(
|
||||
`${outputFilePath}_licenses.json`,
|
||||
licensesOutputFileJson
|
||||
);
|
||||
// worktypesOutputFile
|
||||
const worktypesOutputFileJson = JSON.stringify(worktypesOutputFile);
|
||||
fs.writeFileSync(
|
||||
`${outputFilePath}_worktypes.json`,
|
||||
worktypesOutputFileJson
|
||||
);
|
||||
// AccountsFile配列の出力
|
||||
const accountsFileJson = JSON.stringify(accountsFile);
|
||||
fs.writeFileSync(`${outputFilePath}accounts.json`, accountsFileJson);
|
||||
// UsersFile
|
||||
const usersFileJson = JSON.stringify(usersFile);
|
||||
fs.writeFileSync(`${outputFilePath}users.json`, usersFileJson);
|
||||
// LicensesFile
|
||||
const licensesFileJson = JSON.stringify(licensesFile);
|
||||
fs.writeFileSync(`${outputFilePath}licenses.json`, licensesFileJson);
|
||||
// WorktypesFile
|
||||
const worktypesFileJson = JSON.stringify(worktypesFile);
|
||||
fs.writeFileSync(`${outputFilePath}worktypes.json`, worktypesFileJson);
|
||||
} 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.outputJsonFile.name}`
|
||||
@ -298,12 +367,17 @@ export class transferService {
|
||||
);
|
||||
|
||||
try {
|
||||
// エラー配列を定義
|
||||
let errorArray: string[] = [];
|
||||
// アカウントに対するworktypeのMap配列を作成する
|
||||
const accountWorktypeMap = new Map<string, string[]>();
|
||||
// csvInputFileのバリデーションチェックを行う
|
||||
csvInputFile.forEach((line, index) => {
|
||||
// typeのバリデーションチェック
|
||||
if (
|
||||
line.type !== MIGRATION_TYPE.ADMINISTRATOR &&
|
||||
line.type !== MIGRATION_TYPE.BC &&
|
||||
line.type !== MIGRATION_TYPE.COUNTRY &&
|
||||
line.type !== MIGRATION_TYPE.DISTRIBUTOR &&
|
||||
line.type !== MIGRATION_TYPE.DEALER &&
|
||||
line.type !== MIGRATION_TYPE.CUSTOMER &&
|
||||
@ -314,44 +388,310 @@ 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 (!COUNTRY_LIST.find((country) => country.label === line.country)) {
|
||||
throw new HttpException(
|
||||
`country is invalid. index=${index} country=${line.country}`,
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
if (line.country) {
|
||||
if (!COUNTRY_LIST.find((country) => country.label === line.country)) {
|
||||
throw new HttpException(
|
||||
`country is invalid. index=${index} country=${line.country}`,
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
}
|
||||
// mailのバリデーションチェック
|
||||
// メールアドレスの形式が正しいかどうかのチェック
|
||||
const mailRegExp = new RegExp(
|
||||
/^[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$/
|
||||
);
|
||||
if (!mailRegExp.test(line.email)) {
|
||||
throw new HttpException(
|
||||
`email is invalid. index=${index} email=${line.email}`,
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
const mailRegExp =
|
||||
/^[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]+@[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]*\.[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]*[a-zA-Z]$/;
|
||||
if (line.email) {
|
||||
if (!mailRegExp.test(line.email)) {
|
||||
throw new HttpException(
|
||||
`email is invalid. index=${index} email=${line.email}`,
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
}
|
||||
// recording_modeのバリデーションチェック
|
||||
// RECORDING_MODEに存在するかどうかのチェック
|
||||
if (
|
||||
line.recording_mode !== RECORDING_MODE.DS2_QP &&
|
||||
line.recording_mode !== RECORDING_MODE.DS2_SP &&
|
||||
line.recording_mode !== RECORDING_MODE.DSS &&
|
||||
line.recording_mode !== null
|
||||
) {
|
||||
throw new HttpException(
|
||||
`recording_mode is invalid. index=${index} recording_mode=${line.recording_mode}`,
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
if (line.user_email) {
|
||||
if (!mailRegExp.test(line.user_email)) {
|
||||
throw new HttpException(
|
||||
`user_email is invalid. index=${index} user_email=${line.email}`,
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
}
|
||||
// recording_modeの値が存在する場合
|
||||
if (line.recording_mode) {
|
||||
// recording_modeのバリデーションチェック
|
||||
if (
|
||||
line.recording_mode !== RECORDING_MODE.DS2_QP &&
|
||||
line.recording_mode !== RECORDING_MODE.DS2_SP &&
|
||||
line.recording_mode !== RECORDING_MODE.DSS
|
||||
) {
|
||||
throw new HttpException(
|
||||
`recording_mode is invalid. index=${index} recording_mode=${line.recording_mode}`,
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
}
|
||||
// worktypeの1アカウント20件上限チェック
|
||||
for (let i = 1; i <= WORKTYPE_MAX_COUNT; i++) {
|
||||
const wt = `wt${i}`;
|
||||
if (line[wt]) {
|
||||
if (accountWorktypeMap.has(line.account_id)) {
|
||||
const worktypes = accountWorktypeMap.get(line.account_id);
|
||||
// 重複している場合はPushしない
|
||||
if (worktypes?.includes(line[wt])) {
|
||||
continue;
|
||||
} else {
|
||||
worktypes?.push(line[wt]);
|
||||
}
|
||||
// 20件を超えたらエラー
|
||||
if (worktypes?.length > WORKTYPE_MAX_COUNT) {
|
||||
throw new HttpException(
|
||||
`worktype is over. index=${index} account_id=${line.account_id}`,
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
} else {
|
||||
accountWorktypeMap.set(line.account_id, [line[wt]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// エラー配列に値が存在する場合はエラーファイルを出力する
|
||||
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(
|
||||
makeErrorResponse("E009999"),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${this.validateInputData.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* removeDuplicateEmail
|
||||
* @param accountsFileLines: AccountsFile[]
|
||||
* @param usersFileLines: UsersFile[]
|
||||
* @param licensesFileLines: LicensesFile[]
|
||||
* @returns registInputDataResponse
|
||||
*/
|
||||
async removeDuplicateEmail(
|
||||
context: Context,
|
||||
accountsFileLines: AccountsFile[],
|
||||
usersFileLines: UsersFile[],
|
||||
licensesFileLines: LicensesFile[]
|
||||
): Promise<removeDuplicateEmailResponse> {
|
||||
// パラメータ内容が長大なのでログには出さない
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${this.removeDuplicateEmail.name}`
|
||||
);
|
||||
|
||||
try {
|
||||
const newAccountsFileLines: AccountsFile[] = [];
|
||||
const newUsersFileLines: UsersFile[] = [];
|
||||
const newLicensesFileLines: LicensesFile[] = [...licensesFileLines]; // licensesFileLinesを新規配列にコピー
|
||||
|
||||
// accountsFileLinesの行ループ
|
||||
accountsFileLines.forEach((account) => {
|
||||
const duplicateAdminMail = newAccountsFileLines.find(
|
||||
(a) => a.adminMail.toLowerCase() === account.adminMail.toLowerCase() // メールアドレスは大文字小文字を区別しない
|
||||
);
|
||||
|
||||
if (duplicateAdminMail) {
|
||||
// 重複がある場合はどちらが取込対象か判断できないのでファイルを出力し、エラーにする
|
||||
const errorFileJson = JSON.stringify(account);
|
||||
fs.writeFileSync(`duplicate_error.json`, errorFileJson);
|
||||
throw new HttpException(
|
||||
`adminMail is duplicate. adminMail=${account.adminMail}`,
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
} else {
|
||||
// 重複がない場合
|
||||
newAccountsFileLines.push(account);
|
||||
}
|
||||
});
|
||||
|
||||
// usersFileLinesの行ループ
|
||||
usersFileLines.forEach((user) => {
|
||||
const duplicateUserEmail = newUsersFileLines.find(
|
||||
(u) => u.email.toLowerCase() === user.email.toLowerCase() // メールアドレスは大文字小文字を区別しない
|
||||
);
|
||||
|
||||
if (duplicateUserEmail) {
|
||||
// 重複がある場合
|
||||
const index = newLicensesFileLines.findIndex(
|
||||
(license) =>
|
||||
license.account_id === user.accountId &&
|
||||
license.allocated_user_id === duplicateUserEmail.userId
|
||||
);
|
||||
if (index !== -1) {
|
||||
// ライセンスの割り当てを解除
|
||||
newLicensesFileLines[index].status =
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE;
|
||||
newLicensesFileLines[index].allocated_user_id = null;
|
||||
}
|
||||
} else {
|
||||
// 重複がない場合
|
||||
newUsersFileLines.push(user);
|
||||
}
|
||||
// newAccountsFileLinesとの突合せ
|
||||
const duplicateAdminUserEmail = newAccountsFileLines.find(
|
||||
(a) => a.adminMail.toLowerCase() === user.email.toLowerCase() // メールアドレスは大文字小文字を区別しない
|
||||
);
|
||||
// 重複がある場合
|
||||
if (duplicateAdminUserEmail) {
|
||||
// 同一アカウント内での重複の場合
|
||||
const isDuplicateInSameAccount =
|
||||
duplicateAdminUserEmail.accountId === user.accountId;
|
||||
|
||||
if (isDuplicateInSameAccount) {
|
||||
// アカウント管理者にauthorロールを付与する
|
||||
duplicateAdminUserEmail.role = USER_ROLES.AUTHOR;
|
||||
duplicateAdminUserEmail.authorId = user.authorId;
|
||||
|
||||
// アカウントにライセンスが割り当てられているか確認する
|
||||
const isAllocatedLicense = newLicensesFileLines.some(
|
||||
(license) =>
|
||||
license.account_id === duplicateAdminUserEmail.accountId &&
|
||||
license.allocated_user_id === duplicateAdminUserEmail.userId
|
||||
);
|
||||
// 割り当てられていなければアカウントに割り当てる
|
||||
if (!isAllocatedLicense) {
|
||||
const index = newLicensesFileLines.findIndex(
|
||||
(license) =>
|
||||
license.account_id === user.accountId &&
|
||||
license.allocated_user_id === user.userId
|
||||
);
|
||||
if (index !== -1) {
|
||||
newLicensesFileLines[index].allocated_user_id =
|
||||
duplicateAdminUserEmail.userId;
|
||||
}
|
||||
}
|
||||
}
|
||||
// ユーザーから割り当て解除する
|
||||
const index = newLicensesFileLines.findIndex(
|
||||
(license) =>
|
||||
license.account_id === user.accountId &&
|
||||
license.allocated_user_id === user.userId
|
||||
);
|
||||
if (index !== -1) {
|
||||
// ライセンスの割り当てを解除
|
||||
newLicensesFileLines[index].status =
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE;
|
||||
newLicensesFileLines[index].allocated_user_id = null;
|
||||
}
|
||||
// ユーザーの削除
|
||||
const userIndex = newUsersFileLines.findIndex(
|
||||
(u) => u.userId === user.userId
|
||||
);
|
||||
if (userIndex !== -1) {
|
||||
newUsersFileLines.splice(userIndex, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
accountsFileLines: newAccountsFileLines,
|
||||
usersFileLines: newUsersFileLines,
|
||||
licensesFileLines: newLicensesFileLines,
|
||||
};
|
||||
} 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.removeDuplicateEmail.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import {
|
||||
AccountsOutputFileStep1,
|
||||
LicensesOutputFile,
|
||||
UsersOutputFile,
|
||||
WorktypesOutputFile,
|
||||
AccountsFile,
|
||||
AccountsFileType,
|
||||
LicensesFile,
|
||||
UsersFile,
|
||||
WorktypesFile,
|
||||
} from "src/common/types/types";
|
||||
|
||||
export class transferRequest {
|
||||
@ -15,11 +16,20 @@ export class transferResponse {}
|
||||
|
||||
export class registInputDataResponse {
|
||||
@ApiProperty()
|
||||
accountsOutputFileStep1Lines: AccountsOutputFileStep1[];
|
||||
accountsFileTypeLines: AccountsFileType[];
|
||||
@ApiProperty()
|
||||
usersOutputFileLines: UsersOutputFile[];
|
||||
usersFileLines: UsersFile[];
|
||||
@ApiProperty()
|
||||
licensesOutputFileLines: LicensesOutputFile[];
|
||||
licensesFileLines: LicensesFile[];
|
||||
@ApiProperty()
|
||||
worktypesOutputFileLines: WorktypesOutputFile[];
|
||||
worktypesFileLines: WorktypesFile[];
|
||||
}
|
||||
|
||||
export class removeDuplicateEmailResponse {
|
||||
@ApiProperty()
|
||||
accountsFileLines: AccountsFile[];
|
||||
@ApiProperty()
|
||||
usersFileLines: UsersFile[];
|
||||
@ApiProperty()
|
||||
licensesFileLines: LicensesFile[];
|
||||
}
|
||||
|
||||
@ -74,6 +74,9 @@ export class UsersService {
|
||||
accountId,
|
||||
authorId
|
||||
);
|
||||
this.logger.log(
|
||||
`[${context.getTrackingId()}] isAuthorIdDuplicated=${isAuthorIdDuplicated}`
|
||||
);
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
throw new HttpException(
|
||||
@ -88,9 +91,10 @@ export class UsersService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log("ランダムパスワード生成開始");
|
||||
// ランダムなパスワードを生成する
|
||||
const ramdomPassword = makePassword();
|
||||
this.logger.log("ランダムパスワード生成完了");
|
||||
|
||||
//Azure AD B2Cにユーザーを新規登録する
|
||||
let externalUser: { sub: string } | ConflictError;
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class VerificationRequest {
|
||||
@ApiProperty()
|
||||
inputFilePath: string;
|
||||
}
|
||||
|
||||
export class VerificationResponse {}
|
||||
|
||||
@ -0,0 +1,148 @@
|
||||
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));
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
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 {}
|
||||
@ -0,0 +1,747 @@
|
||||
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;
|
||||
}
|
||||
|
||||
// アカウントID(number)を対応するアカウントID(string)に変換する
|
||||
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について、時はゼロパディングした値で比較する(×01~09 ○1~9)
|
||||
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;
|
||||
}
|
||||
@ -30,14 +30,10 @@ export const isConflictError = (arg: unknown): arg is ConflictError => {
|
||||
export class AdB2cService {
|
||||
private readonly logger = new Logger(AdB2cService.name);
|
||||
private readonly tenantName: string;
|
||||
private readonly flowName: string;
|
||||
private readonly ttl: number;
|
||||
private graphClient: Client;
|
||||
|
||||
constructor(private readonly configService: ConfigService) {
|
||||
this.tenantName = this.configService.getOrThrow<string>("TENANT_NAME");
|
||||
this.flowName = this.configService.getOrThrow<string>("SIGNIN_FLOW_NAME");
|
||||
this.ttl = this.configService.getOrThrow<number>("ADB2C_CACHE_TTL");
|
||||
|
||||
// ADB2Cへの認証情報
|
||||
const credential = new ClientSecretCredential(
|
||||
@ -68,41 +64,57 @@ export class AdB2cService {
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${this.createUser.name}`
|
||||
);
|
||||
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,
|
||||
|
||||
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,
|
||||
},
|
||||
],
|
||||
});
|
||||
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);
|
||||
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);
|
||||
|
||||
// エラーが競合エラーである場合は、メールアドレス重複としてエラーを返す
|
||||
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" };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw e;
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${this.createUser.name}`
|
||||
);
|
||||
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}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,8 +123,10 @@ export class AdB2cService {
|
||||
* @param externalIds
|
||||
* @returns users
|
||||
*/
|
||||
async getUsers(): Promise<AdB2cUser[]> {
|
||||
this.logger.log(`[IN] ${this.getUsers.name}`);
|
||||
async getUsers(
|
||||
context: Context
|
||||
): Promise<{ users: AdB2cUser[]; hasNext: boolean }> {
|
||||
this.logger.log(`[IN] [${context.getTrackingId()}] ${this.getUsers.name}`);
|
||||
|
||||
try {
|
||||
const res: AdB2cResponse = await this.graphClient
|
||||
@ -121,7 +135,7 @@ export class AdB2cService {
|
||||
.filter(`creationType eq 'LocalAccount'`)
|
||||
.get();
|
||||
|
||||
return res.value;
|
||||
return { users: res.value, hasNext: !!res["@odata.nextLink"] };
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
const { statusCode } = e;
|
||||
@ -177,9 +191,11 @@ export class AdB2cService {
|
||||
* Azure AD B2Cからユーザ情報を削除する(複数)
|
||||
* @param externalIds 外部ユーザーID
|
||||
*/
|
||||
async deleteUsers(externalIds: string[]): Promise<void> {
|
||||
async deleteUsers(context: Context, externalIds: string[]): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN]${this.deleteUsers.name} | params: { externalIds: ${externalIds} };`
|
||||
`[IN] [${context.getTrackingId()}] ${
|
||||
this.deleteUsers.name
|
||||
} | params: { externalIds: ${externalIds} };`
|
||||
);
|
||||
|
||||
try {
|
||||
|
||||
@ -89,8 +89,10 @@ export class BlobstorageService {
|
||||
* すべてのコンテナを削除します。
|
||||
* @returns containers
|
||||
*/
|
||||
async deleteContainers(): Promise<void> {
|
||||
this.logger.log(`[IN] ${this.deleteContainers.name}`);
|
||||
async deleteContainers(context: Context): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${this.deleteContainers.name}`
|
||||
);
|
||||
|
||||
try {
|
||||
for await (const container of this.blobServiceClientAU.listContainers({
|
||||
|
||||
@ -1,20 +1,18 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import {
|
||||
DataSource,
|
||||
} from 'typeorm';
|
||||
import { User } from '../users/entity/user.entity';
|
||||
import { Account } from './entity/account.entity';
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { DataSource } from "typeorm";
|
||||
import { User } from "../users/entity/user.entity";
|
||||
import { Account } from "./entity/account.entity";
|
||||
import {
|
||||
getDirection,
|
||||
getTaskListSortableAttribute,
|
||||
} from '../../common/types/sort/util';
|
||||
} from "../../common/types/sort/util";
|
||||
import { SortCriteria } from "../sort_criteria/entity/sort_criteria.entity";
|
||||
import {
|
||||
insertEntity,
|
||||
updateEntity,
|
||||
deleteEntity,
|
||||
} from '../../common/repository';
|
||||
import { Context } from '../../common/log';
|
||||
} from "../../common/repository";
|
||||
import { Context } from "../../common/log";
|
||||
|
||||
@Injectable()
|
||||
export class AccountsRepositoryService {
|
||||
@ -30,6 +28,7 @@ export class AccountsRepositoryService {
|
||||
* @param tier
|
||||
* @param adminExternalUserId
|
||||
* @param adminUserRole
|
||||
* @param adminUserAuthId
|
||||
* @param accountId
|
||||
* @param userId
|
||||
* @param adminUserAcceptedEulaVersion
|
||||
@ -45,6 +44,7 @@ export class AccountsRepositoryService {
|
||||
tier: number,
|
||||
adminExternalUserId: string,
|
||||
adminUserRole: string,
|
||||
adminUserAuthId: string,
|
||||
accountId: number,
|
||||
userId: number,
|
||||
adminUserAcceptedEulaVersion?: string,
|
||||
@ -77,10 +77,12 @@ export class AccountsRepositoryService {
|
||||
user.account_id = persistedAccount.id;
|
||||
user.external_id = adminExternalUserId;
|
||||
user.role = adminUserRole;
|
||||
user.author_id = adminUserAuthId;
|
||||
user.accepted_eula_version = adminUserAcceptedEulaVersion ?? null;
|
||||
user.accepted_privacy_notice_version =
|
||||
adminUserAcceptedPrivacyNoticeVersion ?? null;
|
||||
user.accepted_dpa_version = adminUserAcceptedDpaVersion ?? null;
|
||||
user.email_verified = true;
|
||||
}
|
||||
const usersRepo = entityManager.getRepository(User);
|
||||
const newUser = usersRepo.create(user);
|
||||
@ -161,4 +163,21 @@ 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { DataSource } from "typeorm";
|
||||
import { logger } from "@azure/identity";
|
||||
import { Account } from "./entity/account.entity";
|
||||
import { AUTO_INCREMENT_START } from "../../constants";
|
||||
import { Term } from "./entity/term.entity";
|
||||
import { insertEntities } from "../../common/repository";
|
||||
import { Context } from "../../common/log";
|
||||
|
||||
@Injectable()
|
||||
export class DeleteRepositoryService {
|
||||
// クエリログにコメントを出力するかどうか
|
||||
private readonly isCommentOut = process.env.STAGE !== "local";
|
||||
constructor(private dataSource: DataSource) {}
|
||||
|
||||
/**
|
||||
@ -54,4 +58,35 @@ export class DeleteRepositoryService {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初期データを挿入する
|
||||
* @returns data
|
||||
*/
|
||||
async insertInitData(context: Context): Promise<void> {
|
||||
await this.dataSource.transaction(async (entityManager) => {
|
||||
const termRepo = entityManager.getRepository(Term);
|
||||
|
||||
// ワークフローのデータ作成
|
||||
const newTarmDpa = new Term();
|
||||
newTarmDpa.document_type = "DPA";
|
||||
newTarmDpa.version = "V0.1";
|
||||
const newTarmEula = new Term();
|
||||
newTarmEula.document_type = "EULA";
|
||||
newTarmEula.version = "V0.1";
|
||||
const newTarmPrivacyNotice = new Term();
|
||||
newTarmPrivacyNotice.document_type = "PrivacyNotice";
|
||||
newTarmPrivacyNotice.version = "V0.1";
|
||||
|
||||
const initTerms = [newTarmDpa, newTarmEula, newTarmPrivacyNotice];
|
||||
|
||||
await insertEntities(
|
||||
Term,
|
||||
termRepo,
|
||||
initTerms,
|
||||
this.isCommentOut,
|
||||
context
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,6 +129,33 @@ export class CardLicense {
|
||||
@Column({ nullable: true, type: "datetime" })
|
||||
updated_by: string | null;
|
||||
|
||||
@UpdateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: "datetime",
|
||||
})
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
@Entity({ name: "card_license_issue" })
|
||||
export class CardLicenseIssue {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
issued_at: Date;
|
||||
|
||||
@Column({ nullable: true, type: "datetime" })
|
||||
created_by: string | null;
|
||||
|
||||
@CreateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: "datetime",
|
||||
})
|
||||
created_at: Date;
|
||||
|
||||
@Column({ nullable: true, type: "datetime" })
|
||||
updated_by: string | null;
|
||||
|
||||
@UpdateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: "datetime",
|
||||
|
||||
@ -2,6 +2,7 @@ import { Module } from "@nestjs/common";
|
||||
import { TypeOrmModule } from "@nestjs/typeorm";
|
||||
import {
|
||||
CardLicense,
|
||||
CardLicenseIssue,
|
||||
License,
|
||||
LicenseAllocationHistory,
|
||||
} from "./entity/license.entity";
|
||||
@ -9,7 +10,11 @@ import { LicensesRepositoryService } from "./licenses.repository.service";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([License, CardLicense, LicenseAllocationHistory]),
|
||||
TypeOrmModule.forFeature([
|
||||
License,
|
||||
CardLicense,
|
||||
CardLicenseIssue, LicenseAllocationHistory,
|
||||
]),
|
||||
],
|
||||
providers: [LicensesRepositoryService],
|
||||
exports: [LicensesRepositoryService],
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
import { Injectable, Logger } from "@nestjs/common";
|
||||
import { DataSource, In } from "typeorm";
|
||||
import { DataSource } from "typeorm";
|
||||
import {
|
||||
License,
|
||||
LicenseAllocationHistory,
|
||||
CardLicense,
|
||||
CardLicenseIssue,
|
||||
} from "./entity/license.entity";
|
||||
import { insertEntities } from "../../common/repository";
|
||||
import { insertEntity, insertEntities } from "../../common/repository";
|
||||
import { Context } from "../../common/log";
|
||||
import {
|
||||
LicensesInputFile,
|
||||
CardLicensesInputFile,
|
||||
} from "../../common/types/types";
|
||||
|
||||
import { AUTO_INCREMENT_START } from "../../constants/index";
|
||||
import { LICENSE_ALLOCATED_STATUS, LICENSE_TYPE } from "../../constants";
|
||||
import { CardLicensesFile, LicensesFile } from "src/common/types/types";
|
||||
@Injectable()
|
||||
export class LicensesRepositoryService {
|
||||
//クエリログにコメントを出力するかどうか
|
||||
@ -22,25 +22,27 @@ export class LicensesRepositoryService {
|
||||
/**
|
||||
* ライセンスを登録する
|
||||
* @context Context
|
||||
* @param licensesInputFiles
|
||||
* @param LicensesFiles
|
||||
*/
|
||||
async insertLicenses(
|
||||
context: Context,
|
||||
licensesInputFiles: LicensesInputFile[]
|
||||
LicensesFiles: LicensesFile[]
|
||||
): Promise<{}> {
|
||||
const nowDate = new Date();
|
||||
return await this.dataSource.transaction(async (entityManager) => {
|
||||
const licenseRepo = entityManager.getRepository(License);
|
||||
|
||||
let newLicenses: License[] = [];
|
||||
licensesInputFiles.forEach((licensesInputFile) => {
|
||||
LicensesFiles.forEach((LicensesFile) => {
|
||||
const license = new License();
|
||||
license.account_id = licensesInputFile.account_id;
|
||||
license.status = licensesInputFile.status;
|
||||
license.type = licensesInputFile.type;
|
||||
license.expiry_date = (licensesInputFile.expiry_date) ? new Date(licensesInputFile.expiry_date) : null;
|
||||
if (licensesInputFile.allocated_user_id) {
|
||||
license.allocated_user_id = licensesInputFile.allocated_user_id;
|
||||
license.account_id = LicensesFile.account_id;
|
||||
license.status = LicensesFile.status;
|
||||
license.type = LicensesFile.type;
|
||||
license.expiry_date = LicensesFile.expiry_date
|
||||
? new Date(LicensesFile.expiry_date)
|
||||
: null;
|
||||
if (LicensesFile.allocated_user_id) {
|
||||
license.allocated_user_id = LicensesFile.allocated_user_id;
|
||||
}
|
||||
newLicenses.push(license);
|
||||
});
|
||||
@ -89,30 +91,68 @@ export class LicensesRepositoryService {
|
||||
/**
|
||||
* カードライセンスを登録する
|
||||
* @context Context
|
||||
* @param cardLicensesInputFiles
|
||||
* @param cardLicensesFiles
|
||||
*/
|
||||
async insertCardLicenses(
|
||||
context: Context,
|
||||
cardLicensesInputFiles: CardLicensesInputFile[]
|
||||
cardLicensesFiles: CardLicensesFile[]
|
||||
): Promise<{}> {
|
||||
return await this.dataSource.transaction(async (entityManager) => {
|
||||
const cardLicenseRepo = entityManager.getRepository(CardLicense);
|
||||
const licensesRepo = entityManager.getRepository(License);
|
||||
const cardLicenseIssueRepo =
|
||||
entityManager.getRepository(CardLicenseIssue);
|
||||
|
||||
const licenses: License[] = [];
|
||||
// ライセンステーブルを作成する(BULK INSERT)
|
||||
for (let i = 0; i < cardLicensesFiles.length; i++) {
|
||||
const license = new License();
|
||||
license.account_id = AUTO_INCREMENT_START; // 最初に登場するアカウント(第一アカウント)
|
||||
license.status = LICENSE_ALLOCATED_STATUS.UNALLOCATED;
|
||||
license.type = LICENSE_TYPE.CARD;
|
||||
licenses.push(license);
|
||||
}
|
||||
const savedLicenses = await insertEntities(
|
||||
License,
|
||||
licensesRepo,
|
||||
licenses,
|
||||
this.isCommentOut,
|
||||
context
|
||||
);
|
||||
|
||||
let newCardLicenses: CardLicense[] = [];
|
||||
cardLicensesInputFiles.forEach((cardLicensesInputFile) => {
|
||||
// カードライセンス発行テーブルを作成する
|
||||
const cardLicenseIssue = new CardLicenseIssue();
|
||||
cardLicenseIssue.issued_at = new Date();
|
||||
const newCardLicenseIssue = cardLicenseIssueRepo.create(cardLicenseIssue);
|
||||
const savedCardLicensesIssue = await insertEntity(
|
||||
CardLicenseIssue,
|
||||
cardLicenseIssueRepo,
|
||||
newCardLicenseIssue,
|
||||
this.isCommentOut,
|
||||
context
|
||||
);
|
||||
|
||||
const newCardLicenses: CardLicense[] = [];
|
||||
// カードライセンステーブルを作成する(BULK INSERT)
|
||||
for (let i = 0; i < cardLicensesFiles.length; i++) {
|
||||
const cardLicense = new CardLicense();
|
||||
cardLicense.license_id = cardLicensesInputFile.license_id;
|
||||
cardLicense.issue_id = cardLicensesInputFile.issue_id;
|
||||
cardLicense.card_license_key = cardLicensesInputFile.card_license_key;
|
||||
cardLicense.activated_at = (cardLicensesInputFile.activated_at) ? new Date(cardLicensesInputFile.activated_at) : null;
|
||||
cardLicense.created_at = (cardLicensesInputFile.created_at) ? new Date(cardLicensesInputFile.created_at) : null;
|
||||
cardLicense.created_by = cardLicensesInputFile.created_by;
|
||||
cardLicense.updated_at = (cardLicensesInputFile.updated_at) ? new Date(cardLicensesInputFile.updated_at) : null;
|
||||
cardLicense.updated_by = cardLicensesInputFile.updated_by;
|
||||
cardLicense.license_id = savedLicenses[i].id; // Licenseテーブルの自動採番されたIDを挿入
|
||||
cardLicense.issue_id = savedCardLicensesIssue.id; // CardLicenseIssueテーブルの自動採番されたIDを挿入
|
||||
cardLicense.card_license_key = cardLicensesFiles[i].card_license_key;
|
||||
cardLicense.activated_at = cardLicensesFiles[i].activated_at
|
||||
? new Date(cardLicensesFiles[i].activated_at)
|
||||
: null;
|
||||
cardLicense.created_at = cardLicensesFiles[i].created_at
|
||||
? new Date(cardLicensesFiles[i].created_at)
|
||||
: null;
|
||||
cardLicense.created_by = cardLicensesFiles[i].created_by;
|
||||
cardLicense.updated_at = cardLicensesFiles[i].updated_at
|
||||
? new Date(cardLicensesFiles[i].updated_at)
|
||||
: null;
|
||||
cardLicense.updated_by = cardLicensesFiles[i].updated_by;
|
||||
|
||||
newCardLicenses.push(cardLicense);
|
||||
});
|
||||
}
|
||||
|
||||
const query = cardLicenseRepo
|
||||
.createQueryBuilder()
|
||||
@ -127,4 +167,33 @@ export class LicensesRepositoryService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* ライセンス情報を全件取得する
|
||||
* @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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,4 +138,20 @@ 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ import {
|
||||
import { OptionItem } from "./entity/option_item.entity";
|
||||
import { insertEntities, insertEntity } from "../../common/repository";
|
||||
import { Context } from "../../common/log";
|
||||
import { WorktypesInputFile } from "../../common/types/types";
|
||||
import { WorktypesFile } from "../../common/types/types";
|
||||
|
||||
@Injectable()
|
||||
export class WorktypesRepositoryService {
|
||||
@ -30,15 +30,15 @@ export class WorktypesRepositoryService {
|
||||
*/
|
||||
async createWorktype(
|
||||
context: Context,
|
||||
worktypesInputFiles: WorktypesInputFile[]
|
||||
WorktypesFiles: WorktypesFile[]
|
||||
): Promise<void> {
|
||||
await this.dataSource.transaction(async (entityManager) => {
|
||||
const worktypeRepo = entityManager.getRepository(Worktype);
|
||||
const optionItemRepo = entityManager.getRepository(OptionItem);
|
||||
|
||||
for (const worktypesInputFile of worktypesInputFiles) {
|
||||
const accountId = worktypesInputFile.account_id;
|
||||
const worktypeId = worktypesInputFile.custom_worktype_id;
|
||||
for (const WorktypesFile of WorktypesFiles) {
|
||||
const accountId = WorktypesFile.account_id;
|
||||
const worktypeId = WorktypesFile.custom_worktype_id;
|
||||
const description = null;
|
||||
|
||||
const duplicatedWorktype = await worktypeRepo.findOne({
|
||||
|
||||
@ -17,10 +17,6 @@ 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
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
VITE_STAGE=production
|
||||
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_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_DESK_TOP_APP_SCHEME=odms-desktopapp
|
||||
@ -1,5 +1,5 @@
|
||||
VITE_STAGE=staging
|
||||
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_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_DESK_TOP_APP_SCHEME=odms-desktopapp
|
||||
@ -27,6 +27,7 @@ module.exports = {
|
||||
rules: {
|
||||
"react/jsx-uses-react": "off",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react/require-default-props": "off",
|
||||
"react/function-component-definition": [
|
||||
"error",
|
||||
{
|
||||
|
||||
5
dictation_client/jest.config.js
Normal file
5
dictation_client/jest.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
};
|
||||
6032
dictation_client/package-lock.json
generated
6032
dictation_client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -15,7 +15,8 @@
|
||||
"typecheck": "tsc --noEmit",
|
||||
"codegen": "sh codegen.sh",
|
||||
"lint": "eslint --cache . --ext .js,.ts,.tsx",
|
||||
"lint:fix": "npm run lint -- --fix"
|
||||
"lint:fix": "npm run lint -- --fix",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/msal-browser": "^2.33.0",
|
||||
@ -25,7 +26,6 @@
|
||||
"@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,6 +38,7 @@
|
||||
"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",
|
||||
@ -56,8 +57,10 @@
|
||||
"@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",
|
||||
@ -67,16 +70,18 @@
|
||||
"babel-loader": "^8.2.5",
|
||||
"eslint": "^8.19.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-config-prettier": "^8.10.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.7.1",
|
||||
"prettier": "^2.8.8",
|
||||
"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",
|
||||
@ -99,4 +104,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,13 +11,29 @@ 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に移動する(2024年6月27日)
|
||||
各画面ごとにトークンの期限チェック~自動更新を行う処理を配置していたが、
|
||||
全画面で共通の処理であることと、画面遷移時にチェックのインターバルがリセットされることを考慮し、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 }>) => {
|
||||
@ -73,6 +89,7 @@ const App = (): JSX.Element => {
|
||||
/>
|
||||
<BrowserRouter>
|
||||
<AppRouter />
|
||||
<UpdateTokenTimer />
|
||||
</BrowserRouter>
|
||||
</>
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,8 @@ 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";
|
||||
@ -35,6 +37,8 @@ export const store = configureStore({
|
||||
licenseSummary,
|
||||
licenseOrderHistory,
|
||||
partnerLicense,
|
||||
licenseTrialIssue,
|
||||
searchPartners,
|
||||
dictation,
|
||||
partner,
|
||||
typistGroup,
|
||||
|
||||
18
dictation_client/src/assets/images/change_circle.svg
Normal file
18
dictation_client/src/assets/images/change_circle.svg
Normal file
@ -0,0 +1,18 @@
|
||||
<?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>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
7
dictation_client/src/assets/images/search.svg
Normal file
7
dictation_client/src/assets/images/search.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<?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>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
1
dictation_client/src/assets/images/shuffle.svg
Normal file
1
dictation_client/src/assets/images/shuffle.svg
Normal file
@ -0,0 +1 @@
|
||||
<?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>
|
||||
|
After Width: | Height: | Size: 409 B |
1
dictation_client/src/assets/images/upload.svg
Normal file
1
dictation_client/src/assets/images/upload.svg
Normal file
@ -0,0 +1 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 251 B |
@ -54,6 +54,7 @@ export const errorCodes = [
|
||||
"E010809", // ライセンス発行キャンセル不可エラー(ステータスが変えられている場合)
|
||||
"E010810", // ライセンス発行キャンセル不可エラー(発行から一定期間経過した場合)
|
||||
"E010811", // ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合)
|
||||
"E010812", // ライセンス未割当エラー
|
||||
"E010908", // タイピストグループ不在エラー
|
||||
"E010909", // タイピストグループ名重複エラー
|
||||
"E011001", // ワークタイプ重複エラー
|
||||
@ -62,4 +63,26 @@ 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;
|
||||
|
||||
@ -6,4 +6,4 @@ export type ErrorObject = {
|
||||
statusCode?: number;
|
||||
};
|
||||
|
||||
export type ErrorCodeType = typeof errorCodes[number];
|
||||
export type ErrorCodeType = (typeof errorCodes)[number];
|
||||
|
||||
153
dictation_client/src/common/parser.test.ts
Normal file
153
dictation_client/src/common/parser.test.ts
Normal file
@ -0,0 +1,153 @@
|
||||
/* 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);
|
||||
});
|
||||
});
|
||||
74
dictation_client/src/common/parser.ts
Normal file
74
dictation_client/src/common/parser.ts
Normal file
@ -0,0 +1,74 @@
|
||||
/* 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);
|
||||
},
|
||||
});
|
||||
});
|
||||
2
dictation_client/src/common/test/test_001.csv
Normal file
2
dictation_client/src/common/test/test_001.csv
Normal file
@ -0,0 +1,2 @@
|
||||
name,email,role,author_id,auto_assign,notification,encryption,encryption_password,prompt
|
||||
hoge,sample@example.com,1,"HOGE",1,1,1,abcd,0
|
||||
|
2
dictation_client/src/common/test/test_002.csv
Normal file
2
dictation_client/src/common/test/test_002.csv
Normal file
@ -0,0 +1,2 @@
|
||||
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.
|
2
dictation_client/src/common/test/test_003.csv
Normal file
2
dictation_client/src/common/test/test_003.csv
Normal file
@ -0,0 +1,2 @@
|
||||
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.
|
2
dictation_client/src/common/test/test_004.csv
Normal file
2
dictation_client/src/common/test/test_004.csv
Normal file
@ -0,0 +1,2 @@
|
||||
name,emeil,role,author_id,auto_assign,notification,encryption,encryption_password,prompt
|
||||
hoge,sample@example.com,1,"HOGE",1,1,1,abcd,0
|
||||
|
2
dictation_client/src/common/test/test_005.csv
Normal file
2
dictation_client/src/common/test/test_005.csv
Normal file
@ -0,0 +1,2 @@
|
||||
name,email,role,author_id,auto_assign,notification,encryption,encryption_password,prompt
|
||||
hoge,sample@example.com,1,,1,1,1,abcd,0
|
||||
|
2
dictation_client/src/common/test/test_006.csv
Normal file
2
dictation_client/src/common/test/test_006.csv
Normal file
@ -0,0 +1,2 @@
|
||||
name,email,role,author_id,auto_assign,notification,encryption,encryption_password,prompt
|
||||
hoge,sample@example.com,,"HOGE",1,1,1,abcd,0
|
||||
|
3
dictation_client/src/common/test/test_007.csv
Normal file
3
dictation_client/src/common/test/test_007.csv
Normal file
@ -0,0 +1,3 @@
|
||||
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.
|
2
dictation_client/src/common/test/test_008.csv
Normal file
2
dictation_client/src/common/test/test_008.csv
Normal file
@ -0,0 +1,2 @@
|
||||
name,email,role,author_id,auto_assign,notification,encryption,encryption_password,prompt
|
||||
hoge,sample@example.com,1,1111,1,1,1,222222,0
|
||||
|
@ -47,6 +47,7 @@ export const KEYS_TO_PRESERVE = [
|
||||
"accessToken",
|
||||
"refreshToken",
|
||||
"displayInfo",
|
||||
"filterCriteria",
|
||||
"sortCriteria",
|
||||
];
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useCallback } from "react";
|
||||
import React, { useCallback, useLayoutEffect, useState } from "react";
|
||||
import { AppDispatch } from "app/store";
|
||||
import { decodeToken } from "common/decodeToken";
|
||||
import { useInterval } from "common/useInterval";
|
||||
@ -17,41 +17,58 @@ 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);
|
||||
|
||||
// 期限が5分以内であれば更新APIを呼ぶ
|
||||
const updateToken = useCallback(async () => {
|
||||
// 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 (isUpdating) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 代行操作トークン更新処理
|
||||
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");
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 代行操作トークン更新処理
|
||||
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);
|
||||
}
|
||||
}, [dispatch, delegattionToken, navigate]);
|
||||
}, [isUpdating, delegattionToken, dispatch, navigate]);
|
||||
useLayoutEffect(() => {
|
||||
updateToken();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useInterval(updateToken, TOKEN_UPDATE_INTERVAL_MS);
|
||||
|
||||
|
||||
@ -6,8 +6,50 @@ import { getTranslationID } from "translation";
|
||||
const Footer: React.FC = () => {
|
||||
const [t] = useTranslation();
|
||||
return (
|
||||
<footer className={`${styles.footer}`}>
|
||||
<div>{t(getTranslationID("common.label.copyRight"))}</div>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
updateAccountInfoAsync,
|
||||
getAccountRelationsAsync,
|
||||
deleteAccountAsync,
|
||||
updateFileDeleteSettingAsync,
|
||||
} from "./operations";
|
||||
|
||||
const initialState: AccountState = {
|
||||
@ -15,6 +16,8 @@ const initialState: AccountState = {
|
||||
tier: 0,
|
||||
country: "",
|
||||
delegationPermission: false,
|
||||
autoFileDelete: false,
|
||||
fileRetentionDays: 0,
|
||||
},
|
||||
},
|
||||
dealers: [],
|
||||
@ -29,6 +32,8 @@ const initialState: AccountState = {
|
||||
secondryAdminUserId: undefined,
|
||||
},
|
||||
isLoading: false,
|
||||
autoFileDelete: false,
|
||||
fileRetentionDays: 0,
|
||||
},
|
||||
};
|
||||
|
||||
@ -64,6 +69,20 @@ 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;
|
||||
},
|
||||
@ -85,6 +104,10 @@ 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) => {
|
||||
@ -99,6 +122,15 @@ 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;
|
||||
});
|
||||
@ -115,6 +147,8 @@ export const {
|
||||
changeDealerPermission,
|
||||
changePrimaryAdministrator,
|
||||
changeSecondryAdministrator,
|
||||
changeAutoFileDelete,
|
||||
changeFileRetentionDays,
|
||||
cleanupApps,
|
||||
} = accountSlice.actions;
|
||||
export default accountSlice.reducer;
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
UpdateAccountInfoRequest,
|
||||
UsersApi,
|
||||
DeleteAccountRequest,
|
||||
UpdateFileDeleteSettingRequest,
|
||||
} from "../../api/api";
|
||||
import { Configuration } from "../../api/configuration";
|
||||
import { ViewAccountRelationsInfo } from "./types";
|
||||
@ -38,7 +39,7 @@ export const getAccountRelationsAsync = createAsyncThunk<
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
const dealers = await accountsApi.getDealers();
|
||||
const users = await usersApi.getUsers({
|
||||
const users = await usersApi.getUsers(undefined, undefined, {
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
return {
|
||||
@ -112,6 +113,58 @@ 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 */
|
||||
|
||||
@ -16,3 +16,18 @@ 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,
|
||||
};
|
||||
};
|
||||
|
||||
@ -19,4 +19,6 @@ export interface Domain {
|
||||
export interface Apps {
|
||||
updateAccountInfo: UpdateAccountInfoRequest;
|
||||
isLoading: boolean;
|
||||
autoFileDelete: boolean;
|
||||
fileRetentionDays: number;
|
||||
}
|
||||
|
||||
@ -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 =>
|
||||
|
||||
@ -11,6 +11,8 @@ import {
|
||||
playbackAsync,
|
||||
updateAssigneeAsync,
|
||||
cancelAsync,
|
||||
deleteTaskAsync,
|
||||
renameFileAsync,
|
||||
} from "./operations";
|
||||
import {
|
||||
SORTABLE_COLUMN,
|
||||
@ -41,6 +43,8 @@ const initialState: DictationState = {
|
||||
direction: DIRECTION.ASC,
|
||||
paramName: SORTABLE_COLUMN.JobNumber,
|
||||
selectedTask: undefined,
|
||||
authorId: "",
|
||||
fileName: "",
|
||||
assignee: {
|
||||
selected: [],
|
||||
pool: [],
|
||||
@ -76,6 +80,14 @@ 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;
|
||||
@ -218,6 +230,25 @@ 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;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@ -225,6 +256,8 @@ export const {
|
||||
changeDisplayInfo,
|
||||
changeDirection,
|
||||
changeParamName,
|
||||
changeAuthorId,
|
||||
changeFileName,
|
||||
changeSelectedTask,
|
||||
changeAssignee,
|
||||
changeBackupTaskChecked,
|
||||
|
||||
@ -35,6 +35,8 @@ export const listTasksAsync = createAsyncThunk<
|
||||
filter?: string;
|
||||
direction: DirectionType;
|
||||
paramName: SortableColumnType;
|
||||
authorId?: string;
|
||||
fileName?: string;
|
||||
},
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
@ -43,7 +45,8 @@ export const listTasksAsync = createAsyncThunk<
|
||||
};
|
||||
}
|
||||
>("dictations/listTasksAsync", async (args, thunkApi) => {
|
||||
const { limit, offset, filter, direction, paramName } = args;
|
||||
const { limit, offset, filter, direction, paramName, authorId, fileName } =
|
||||
args;
|
||||
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
@ -60,6 +63,8 @@ export const listTasksAsync = createAsyncThunk<
|
||||
filter,
|
||||
direction,
|
||||
paramName,
|
||||
authorId,
|
||||
fileName,
|
||||
{
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
}
|
||||
@ -80,6 +85,136 @@ 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;
|
||||
@ -280,6 +415,8 @@ export const playbackAsync = createAsyncThunk<
|
||||
direction: DirectionType;
|
||||
paramName: SortableColumnType;
|
||||
audioFileId: number;
|
||||
filterConditionAuthorId: string;
|
||||
filterConditionFileName: string;
|
||||
},
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
@ -288,7 +425,13 @@ export const playbackAsync = createAsyncThunk<
|
||||
};
|
||||
}
|
||||
>("dictations/playbackAsync", async (args, thunkApi) => {
|
||||
const { audioFileId, direction, paramName } = args;
|
||||
const {
|
||||
audioFileId,
|
||||
direction,
|
||||
paramName,
|
||||
filterConditionAuthorId,
|
||||
filterConditionFileName,
|
||||
} = args;
|
||||
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
@ -305,6 +448,12 @@ 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}` },
|
||||
});
|
||||
@ -343,6 +492,30 @@ 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({
|
||||
@ -363,6 +536,8 @@ export const cancelAsync = createAsyncThunk<
|
||||
paramName: SortableColumnType;
|
||||
audioFileId: number;
|
||||
isTypist: boolean;
|
||||
filterConditionAuthorId: string;
|
||||
filterConditionFileName: string;
|
||||
},
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
@ -371,7 +546,14 @@ export const cancelAsync = createAsyncThunk<
|
||||
};
|
||||
}
|
||||
>("dictations/cancelAsync", async (args, thunkApi) => {
|
||||
const { audioFileId, direction, paramName, isTypist } = args;
|
||||
const {
|
||||
audioFileId,
|
||||
direction,
|
||||
paramName,
|
||||
isTypist,
|
||||
filterConditionAuthorId,
|
||||
filterConditionFileName,
|
||||
} = args;
|
||||
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
@ -382,15 +564,25 @@ export const cancelAsync = createAsyncThunk<
|
||||
const tasksApi = new TasksApi(config);
|
||||
const usersApi = new UsersApi(config);
|
||||
try {
|
||||
// ユーザーがタイピストである場合に、ソート条件を保存する
|
||||
// ユーザーがタイピストである場合に、ソート条件と検索条件を保存する
|
||||
if (isTypist) {
|
||||
await usersApi.updateSortCriteria(
|
||||
{ direction, paramName },
|
||||
{
|
||||
direction,
|
||||
paramName,
|
||||
},
|
||||
{
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
}
|
||||
);
|
||||
await usersApi.updateTaskFilter(
|
||||
{ filterConditionAuthorId, filterConditionFileName },
|
||||
{
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
await tasksApi.cancel(audioFileId, {
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
@ -426,6 +618,93 @@ 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,
|
||||
{
|
||||
@ -456,6 +735,8 @@ export const listBackupPopupTasksAsync = createAsyncThunk<
|
||||
BACKUP_POPUP_LIST_STATUS.join(","), // ステータスはFinished,Backupのみ
|
||||
DIRECTION.DESC,
|
||||
SORTABLE_COLUMN.Status,
|
||||
undefined, // backupポップアップ表示時には検索条件は未指定
|
||||
undefined, // backupポップアップ表示時には検索条件は未指定
|
||||
{
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
}
|
||||
@ -541,10 +822,21 @@ export const backupTasksAsync = createAsyncThunk<
|
||||
a.click();
|
||||
a.parentNode?.removeChild(a);
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await tasksApi.backup(task.audioFileId, {
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
// バックアップ済みに更新
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -556,8 +848,22 @@ 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",
|
||||
@ -568,3 +874,143 @@ 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 });
|
||||
}
|
||||
});
|
||||
|
||||
@ -72,6 +72,12 @@ 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;
|
||||
|
||||
|
||||
@ -25,6 +25,8 @@ export interface Apps {
|
||||
displayInfo: DisplayInfoType;
|
||||
direction: DirectionType;
|
||||
paramName: SortableColumnType;
|
||||
authorId: string;
|
||||
fileName: string;
|
||||
selectedTask?: Task;
|
||||
selectedFileTask?: Task;
|
||||
assignee: {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { LicenseCardActivateState } from "./state";
|
||||
import { activateCardLicenseAsync } from "./operations";
|
||||
|
||||
const initialState: LicenseCardActivateState = {
|
||||
apps: {
|
||||
@ -14,6 +15,17 @@ 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;
|
||||
|
||||
@ -7,3 +7,8 @@ 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;
|
||||
|
||||
@ -3,7 +3,12 @@ import type { RootState } from "app/store";
|
||||
import { getTranslationID } from "translation";
|
||||
import { openSnackbar } from "features/ui/uiSlice";
|
||||
import { getAccessToken } from "features/auth";
|
||||
import { AccountsApi, LicensesApi } from "../../../api/api";
|
||||
import {
|
||||
AccountsApi,
|
||||
LicensesApi,
|
||||
SearchPartner,
|
||||
PartnerLicenseInfo,
|
||||
} from "../../../api/api";
|
||||
import { Configuration } from "../../../api/configuration";
|
||||
import { ErrorObject, createErrorObject } from "../../../common/errors";
|
||||
import { OrderHistoryView } from "./types";
|
||||
@ -15,6 +20,7 @@ export const getLicenseOrderHistoriesAsync = createAsyncThunk<
|
||||
// パラメータ
|
||||
limit: number;
|
||||
offset: number;
|
||||
selectedRow?: PartnerLicenseInfo | SearchPartner;
|
||||
},
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
@ -23,7 +29,7 @@ export const getLicenseOrderHistoriesAsync = createAsyncThunk<
|
||||
};
|
||||
}
|
||||
>("licenses/licenseOrderHisotyAsync", async (args, thunkApi) => {
|
||||
const { limit, offset } = args;
|
||||
const { limit, offset, selectedRow } = args;
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
@ -33,7 +39,6 @@ export const getLicenseOrderHistoriesAsync = createAsyncThunk<
|
||||
const accountsApi = new AccountsApi(config);
|
||||
|
||||
try {
|
||||
const { selectedRow } = state.partnerLicense.apps;
|
||||
let accountId = 0;
|
||||
let companyName = "";
|
||||
// 他の画面から指定されていない場合はログインアカウントのidを取得する
|
||||
@ -46,7 +51,9 @@ export const getLicenseOrderHistoriesAsync = createAsyncThunk<
|
||||
companyName = getMyAccountResponse.data.account.companyName;
|
||||
} else {
|
||||
accountId = selectedRow.accountId;
|
||||
companyName = selectedRow.companyName;
|
||||
// パートナーライセンスとパートナー検索で型が異なるため、型ガードで推論させる
|
||||
if ("companyName" in selectedRow) companyName = selectedRow.companyName;
|
||||
if ("name" in selectedRow) companyName = selectedRow.name;
|
||||
}
|
||||
|
||||
const res = await accountsApi.getOrderHistories(
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { LicenseSummaryState } from "./state";
|
||||
import { getCompanyNameAsync, getLicenseSummaryAsync } from "./operations";
|
||||
import {
|
||||
getCompanyNameAsync,
|
||||
getLicenseSummaryAsync,
|
||||
updateRestrictionStatusAsync,
|
||||
} from "./operations";
|
||||
|
||||
const initialState: LicenseSummaryState = {
|
||||
domain: {
|
||||
@ -35,12 +39,30 @@ 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;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -7,7 +7,9 @@ import {
|
||||
AccountsApi,
|
||||
GetCompanyNameResponse,
|
||||
GetLicenseSummaryResponse,
|
||||
SearchPartner,
|
||||
PartnerLicenseInfo,
|
||||
UpdateRestrictionStatusRequest,
|
||||
} from "../../../api/api";
|
||||
import { Configuration } from "../../../api/configuration";
|
||||
import { ErrorObject, createErrorObject } from "../../../common/errors";
|
||||
@ -16,7 +18,7 @@ export const getLicenseSummaryAsync = createAsyncThunk<
|
||||
// 正常時の戻り値の型
|
||||
GetLicenseSummaryResponse,
|
||||
// 引数
|
||||
{ selectedRow?: PartnerLicenseInfo },
|
||||
{ selectedRow?: PartnerLicenseInfo | SearchPartner },
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
@ -72,7 +74,7 @@ export const getCompanyNameAsync = createAsyncThunk<
|
||||
// 正常時の戻り値の型
|
||||
GetCompanyNameResponse,
|
||||
// 引数
|
||||
{ selectedRow?: PartnerLicenseInfo },
|
||||
{ selectedRow?: PartnerLicenseInfo | SearchPartner },
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
@ -123,3 +125,58 @@ 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 });
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { RootState } from "app/store";
|
||||
|
||||
// 各値はそのまま画面に表示するので、licenseSummaryInfoとして値を取得する
|
||||
export const selecLicenseSummaryInfo = (state: RootState) =>
|
||||
export const selectLicenseSummaryInfo = (state: RootState) =>
|
||||
state.licenseSummary.domain.licenseSummaryInfo;
|
||||
|
||||
export const selectCompanyName = (state: RootState) =>
|
||||
state.licenseSummary.domain.accountInfo.companyName;
|
||||
|
||||
export const selectIsLoading = (state: RootState) => state.license;
|
||||
export const selectIsLoading = (state: RootState) =>
|
||||
state.licenseSummary.apps.isLoading;
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
export const ISSUED_TRIAL_LICENSE_QUANTITY = 10;
|
||||
export const TRIAL_LICENSE_EXPIRATION_DAY = 30;
|
||||
@ -0,0 +1,5 @@
|
||||
export * from "./state";
|
||||
export * from "./operations";
|
||||
export * from "./selectors";
|
||||
export * from "./licenseTrialIssueSlice";
|
||||
export * from "./constants";
|
||||
@ -0,0 +1,60 @@
|
||||
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;
|
||||
@ -0,0 +1,84 @@
|
||||
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 });
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,10 @@
|
||||
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;
|
||||
@ -0,0 +1,9 @@
|
||||
export interface LicenseTrialIssueState {
|
||||
apps: Apps;
|
||||
}
|
||||
|
||||
export interface Apps {
|
||||
isLoading: boolean;
|
||||
expirationDate: string;
|
||||
quantity: number;
|
||||
}
|
||||
@ -105,3 +105,82 @@ 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 });
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||
import { PartnerLicenseInfo } from "api";
|
||||
import { PartnerLicensesState, HierarchicalElement } from "./state";
|
||||
import { getMyAccountAsync, getPartnerLicenseAsync } from "./operations";
|
||||
import {
|
||||
getMyAccountAsync,
|
||||
getPartnerLicenseAsync,
|
||||
switchParentAsync,
|
||||
} from "./operations";
|
||||
import { ACCOUNTS_VIEW_LIMIT } from "./constants";
|
||||
|
||||
const initialState: PartnerLicensesState = {
|
||||
@ -12,6 +16,8 @@ const initialState: PartnerLicensesState = {
|
||||
tier: 0,
|
||||
country: "",
|
||||
delegationPermission: false,
|
||||
autoFileDelete: false,
|
||||
fileRetentionDays: 0,
|
||||
},
|
||||
total: 0,
|
||||
ownPartnerLicense: {
|
||||
@ -19,6 +25,7 @@ const initialState: PartnerLicensesState = {
|
||||
tier: 0,
|
||||
companyName: "",
|
||||
stockLicense: 0,
|
||||
allocatedLicense: 0,
|
||||
issuedRequested: 0,
|
||||
shortage: 0,
|
||||
issueRequesting: 0,
|
||||
@ -33,6 +40,9 @@ const initialState: PartnerLicensesState = {
|
||||
hierarchicalElements: [],
|
||||
isLoading: true,
|
||||
selectedRow: undefined,
|
||||
isLicenseOrderHistoryOpen: false,
|
||||
isViewDetailsOpen: false,
|
||||
isSearchPopupOpen: false,
|
||||
},
|
||||
};
|
||||
|
||||
@ -61,6 +71,9 @@ export const partnerLicenseSlice = createSlice({
|
||||
const { deleteCount } = action.payload;
|
||||
state.apps.hierarchicalElements.splice(-deleteCount);
|
||||
},
|
||||
clearHierarchicalElement: (state) => {
|
||||
state.apps.hierarchicalElements = [];
|
||||
},
|
||||
changeSelectedRow: (
|
||||
state,
|
||||
action: PayloadAction<{ value?: PartnerLicenseInfo }>
|
||||
@ -79,6 +92,24 @@ 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) => {
|
||||
@ -104,14 +135,27 @@ 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;
|
||||
|
||||
@ -30,3 +30,10 @@ 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;
|
||||
|
||||
@ -20,6 +20,9 @@ export interface Apps {
|
||||
hierarchicalElements: HierarchicalElement[];
|
||||
isLoading: boolean;
|
||||
selectedRow?: PartnerLicenseInfo;
|
||||
isLicenseOrderHistoryOpen: boolean;
|
||||
isViewDetailsOpen: boolean;
|
||||
isSearchPopupOpen: boolean;
|
||||
}
|
||||
|
||||
export interface HierarchicalElement {
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
export * from "./state";
|
||||
export * from "./operations";
|
||||
export * from "./selectors";
|
||||
export * from "./searchPartnerSlice";
|
||||
@ -0,0 +1,96 @@
|
||||
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 });
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,80 @@
|
||||
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;
|
||||
@ -0,0 +1,14 @@
|
||||
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;
|
||||
18
dictation_client/src/features/license/searchPartner/state.ts
Normal file
18
dictation_client/src/features/license/searchPartner/state.ts
Normal file
@ -0,0 +1,18 @@
|
||||
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;
|
||||
}
|
||||
@ -8,6 +8,8 @@ import {
|
||||
AccountsApi,
|
||||
CreatePartnerAccountRequest,
|
||||
GetPartnersResponse,
|
||||
DeletePartnerAccountRequest,
|
||||
GetPartnerUsersResponse,
|
||||
} from "../../api/api";
|
||||
import { Configuration } from "../../api/configuration";
|
||||
|
||||
@ -116,3 +118,170 @@ 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 });
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { PartnerState } from "./state";
|
||||
import { createPartnerAccountAsync, getPartnerInfoAsync } from "./operations";
|
||||
import {
|
||||
createPartnerAccountAsync,
|
||||
getPartnerInfoAsync,
|
||||
deletePartnerAccountAsync,
|
||||
getPartnerUsersAsync,
|
||||
editPartnerInfoAsync,
|
||||
} from "./operations";
|
||||
import { LIMIT_PARTNER_VIEW_NUM } from "./constants";
|
||||
|
||||
const initialState: PartnerState = {
|
||||
@ -17,6 +23,13 @@ const initialState: PartnerState = {
|
||||
adminName: "",
|
||||
email: "",
|
||||
},
|
||||
editPartner: {
|
||||
users: [],
|
||||
id: 0,
|
||||
companyName: "",
|
||||
country: "",
|
||||
selectedAdminId: 0,
|
||||
},
|
||||
limit: LIMIT_PARTNER_VIEW_NUM,
|
||||
offset: 0,
|
||||
isLoading: false,
|
||||
@ -75,6 +88,37 @@ 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) => {
|
||||
@ -97,6 +141,37 @@ 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 {
|
||||
@ -108,5 +183,9 @@ export const {
|
||||
savePageInfo,
|
||||
changeDelegateAccount,
|
||||
cleanupDelegateAccount,
|
||||
changeEditPartner,
|
||||
changeEditCompanyName,
|
||||
changeSelectedAdminId,
|
||||
cleanupPartnerAccount,
|
||||
} = partnerSlice.actions;
|
||||
export default partnerSlice.reducer;
|
||||
|
||||
@ -62,3 +62,17 @@ 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;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import {
|
||||
CreatePartnerAccountRequest,
|
||||
GetPartnersResponse,
|
||||
PartnerUser,
|
||||
} from "../../api/api";
|
||||
|
||||
export interface PartnerState {
|
||||
@ -19,4 +20,11 @@ export interface Apps {
|
||||
isLoading: boolean;
|
||||
delegatedAccountId?: number;
|
||||
delegatedCompanyName?: string;
|
||||
editPartner: {
|
||||
users: PartnerUser[];
|
||||
id: number;
|
||||
companyName: string;
|
||||
country: string;
|
||||
selectedAdminId: number;
|
||||
};
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user