From c304271494082bdf9f5261b0a5f34f11d37fce3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B0=B4=E6=9C=AC=20=E7=A5=90=E5=B8=8C?= Date: Tue, 8 Aug 2023 04:46:59 +0000 Subject: [PATCH 01/40] =?UTF-8?q?Merged=20PR=20302:=20=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=88=E6=B3=A8=E6=96=87=E5=B1=A5=E6=AD=B4?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2339: 画面修正(注文履歴)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2339) 注文履歴画面について、以下の修正を行いました。 ・Issueボタン押下時の処理を追加 ・Issueボタン、Issue Cancelボタン、Order Cancelボタンのレイアウト修正 ※Issue Cancelボタン、Order Cancelボタン押下時の動作は今回は対象外です。 ## レビューポイント なし ## UIの変更 https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task2339?csf=1&web=1&e=sUjf1e ## 動作確認状況 ローカルで動作確認済み ## 補足 なし --- dictation_client/src/common/errors/code.ts | 2 + .../licenseOrderHistorySlice.ts | 11 +- .../license/licenseOrderHistory/operations.ts | 67 ++++++++ .../pages/LicensePage/licenseOrderHistory.tsx | 155 +++++++++++------- dictation_client/src/translation/de.json | 4 + dictation_client/src/translation/en.json | 4 + dictation_client/src/translation/es.json | 4 + dictation_client/src/translation/fr.json | 4 + 8 files changed, 192 insertions(+), 59 deletions(-) diff --git a/dictation_client/src/common/errors/code.ts b/dictation_client/src/common/errors/code.ts index 9d3f7d3..2fb3510 100644 --- a/dictation_client/src/common/errors/code.ts +++ b/dictation_client/src/common/errors/code.ts @@ -42,4 +42,6 @@ export const errorCodes = [ "E010701", // Blobファイル不在エラー "E010801", // ライセンス不在エラー "E010802", // ライセンス取り込み済みエラー + "E010803", // ライセンス数不足エラー + "E010804", // ライセンス発行済みエラー ] as const; diff --git a/dictation_client/src/features/license/licenseOrderHistory/licenseOrderHistorySlice.ts b/dictation_client/src/features/license/licenseOrderHistory/licenseOrderHistorySlice.ts index d86f33c..27d5771 100644 --- a/dictation_client/src/features/license/licenseOrderHistory/licenseOrderHistorySlice.ts +++ b/dictation_client/src/features/license/licenseOrderHistory/licenseOrderHistorySlice.ts @@ -1,6 +1,6 @@ import { PayloadAction, createSlice } from "@reduxjs/toolkit"; import { LicenseOrderHistoryState } from "./state"; -import { getLicenseOrderHistoriesAsync } from "./operations"; +import { getLicenseOrderHistoriesAsync, issueLicenseAsync } from "./operations"; import { LIMIT_ORDER_HISORY_NUM } from "./constants"; const initialState: LicenseOrderHistoryState = { @@ -52,6 +52,15 @@ export const licenseOrderHistorySlice = createSlice({ builder.addCase(getLicenseOrderHistoriesAsync.rejected, (state) => { state.apps.isLoading = false; }); + builder.addCase(issueLicenseAsync.pending, (state) => { + state.apps.isLoading = true; + }); + builder.addCase(issueLicenseAsync.fulfilled, (state) => { + state.apps.isLoading = false; + }); + builder.addCase(issueLicenseAsync.rejected, (state) => { + state.apps.isLoading = false; + }); }, }); diff --git a/dictation_client/src/features/license/licenseOrderHistory/operations.ts b/dictation_client/src/features/license/licenseOrderHistory/operations.ts index 75632d5..b6116b0 100644 --- a/dictation_client/src/features/license/licenseOrderHistory/operations.ts +++ b/dictation_client/src/features/license/licenseOrderHistory/operations.ts @@ -74,3 +74,70 @@ export const getLicenseOrderHistoriesAsync = createAsyncThunk< return thunkApi.rejectWithValue({ error }); } }); + +export const issueLicenseAsync = createAsyncThunk< + { + /* Empty Object */ + }, + { + // パラメータ + orderedAccountId: number; + poNumber: string; + }, + { + // rejectした時の返却値の型 + rejectValue: { + error: ErrorObject; + }; + } +>("licenses/issueLicenseAsync", async (args, thunkApi) => { + const { orderedAccountId, poNumber } = args; + // apiのConfigurationを取得する + const { getState } = thunkApi; + const state = getState() as RootState; + const { configuration, accessToken } = state.auth; + const config = new Configuration(configuration); + const accountsApi = new AccountsApi(config); + + try { + await accountsApi.issueLicense( + { + orderedAccountId, + poNumber, + }, + { + headers: { authorization: `Bearer ${accessToken}` }, + } + ); + thunkApi.dispatch( + openSnackbar({ + level: "info", + message: getTranslationID("common.message.success"), + }) + ); + return {}; + } catch (e) { + // e ⇒ errorObjectに変換" + const error = createErrorObject(e); + + let errorMessage = getTranslationID("common.message.internalServerError"); + + if (error.code === "E010803") { + errorMessage = getTranslationID( + "orderHistoriesPage.message.notEnoughOfNumberOfLicense" + ); + } else if (error.code === "E010804") { + errorMessage = getTranslationID( + "orderHistoriesPage.message.alreadyIssueLicense" + ); + } + thunkApi.dispatch( + openSnackbar({ + level: "error", + message: errorMessage, + }) + ); + + return thunkApi.rejectWithValue({ error }); + } +}); diff --git a/dictation_client/src/pages/LicensePage/licenseOrderHistory.tsx b/dictation_client/src/pages/LicensePage/licenseOrderHistory.tsx index 8713d6e..ce8beb8 100644 --- a/dictation_client/src/pages/LicensePage/licenseOrderHistory.tsx +++ b/dictation_client/src/pages/LicensePage/licenseOrderHistory.tsx @@ -17,6 +17,7 @@ import { selectOrderHisory, selectTotal, selectTotalPage, + issueLicenseAsync, selectOffset, savePageInfo, selectCompanyName, @@ -50,6 +51,40 @@ export const LicenseOrderHistory: React.FC = ( onReturn(); }, [isLoading, onReturn]); + // issue,issueCancel,orderCancelボタンの処理終了時の情報更新用 + const UpdateHistoriesList = () => { + dispatch( + getLicenseOrderHistoriesAsync({ + limit: LIMIT_ORDER_HISORY_NUM, + offset, + }) + ); + }; + + // issueボタンを押下時の処理 + const issueLicense = useCallback( + async (poNumber: string) => { + // ダイアログ確認 + // eslint-disable-next-line no-alert + if (window.confirm(t(getTranslationID("common.message.dialogConfirm")))) { + // 注文APIの呼び出し + if (selectedRow) { + const { meta } = await dispatch( + issueLicenseAsync({ + orderedAccountId: selectedRow.accountId, + poNumber, + }) + ); + if (meta.requestStatus === "fulfilled") { + UpdateHistoriesList(); + } + } + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [dispatch] + ); + // ページネーションのボタンクリック時のアクション const movePage = (targetOffset: number) => { dispatch( @@ -130,7 +165,6 @@ export const LicenseOrderHistory: React.FC = ( {t(getTranslationID("orderHistoriesPage.label.status"))} - {!isLoading && licenseOrderHistory.length !== 0 && @@ -168,63 +202,68 @@ export const LicenseOrderHistory: React.FC = ( })()} - {selectedRow === undefined && - x.status === STATUS.ISSUE_REQESTING && ( - - )} - {selectedRow !== undefined && - x.status === STATUS.ISSUE_REQESTING && ( - - )} - {selectedRow !== undefined && - x.status === STATUS.ISSUED && ( - - )} + {!selectedRow && ( + + )} + {selectedRow && ( + + )} ))} diff --git a/dictation_client/src/translation/de.json b/dictation_client/src/translation/de.json index 54bb849..89ebc3d 100644 --- a/dictation_client/src/translation/de.json +++ b/dictation_client/src/translation/de.json @@ -324,6 +324,10 @@ "issueCancel": "(de)Issue Cancel", "orderCancel": "(de)Order Cancel", "histories": "(de)histories" + }, + "message": { + "notEnoughOfNumberOfLicense": "(de)ライセンスが不足しているため、発行することができませんでした。ライセンスの注文を行ってください。", + "alreadyIssueLicense": "(de)すでに発行済みの注文です。画面を更新してください。" } } } diff --git a/dictation_client/src/translation/en.json b/dictation_client/src/translation/en.json index 44ba9e9..20239ea 100644 --- a/dictation_client/src/translation/en.json +++ b/dictation_client/src/translation/en.json @@ -324,6 +324,10 @@ "issueCancel": "Issue Cancel", "orderCancel": "Order Cancel", "histories": "histories" + }, + "message": { + "notEnoughOfNumberOfLicense": "ライセンスが不足しているため、発行することができませんでした。ライセンスの注文を行ってください。", + "alreadyIssueLicense": "すでに発行済みの注文です。画面を更新してください。" } } } diff --git a/dictation_client/src/translation/es.json b/dictation_client/src/translation/es.json index 0394154..19a21f8 100644 --- a/dictation_client/src/translation/es.json +++ b/dictation_client/src/translation/es.json @@ -324,6 +324,10 @@ "issueCancel": "(es)Issue Cancel", "orderCancel": "(es)Order Cancel", "histories": "(es)histories" + }, + "message": { + "notEnoughOfNumberOfLicense": "(es)ライセンスが不足しているため、発行することができませんでした。ライセンスの注文を行ってください。", + "alreadyIssueLicense": "(es)すでに発行済みの注文です。画面を更新してください。" } } } diff --git a/dictation_client/src/translation/fr.json b/dictation_client/src/translation/fr.json index a1d7e74..e1992b2 100644 --- a/dictation_client/src/translation/fr.json +++ b/dictation_client/src/translation/fr.json @@ -324,6 +324,10 @@ "issueCancel": "(fr)Issue Cancel", "orderCancel": "(fr)Order Cancel", "histories": "(fr)histories" + }, + "message": { + "notEnoughOfNumberOfLicense": "(fr)ライセンスが不足しているため、発行することができませんでした。ライセンスの注文を行ってください。", + "alreadyIssueLicense": "(fr)すでに発行済みの注文です。画面を更新してください。" } } } From 98e9937d9d6eaad0a6822327876a10b2b75043a9 Mon Sep 17 00:00:00 2001 From: "oura.a" Date: Tue, 8 Aug 2023 08:11:56 +0000 Subject: [PATCH 02/40] =?UTF-8?q?Merged=20PR=20313:=20=E7=AC=AC=E4=BA=94?= =?UTF-8?q?=E9=9A=8E=E5=B1=A4=E3=81=AEShortage=E3=81=AB=E3=81=A4=E3=81=84?= =?UTF-8?q?=E3=81=A6=E6=95=B0=E5=80=A4=E3=82=92licensesummary=E3=81=AE?= =?UTF-8?q?=E3=82=82=E3=81=AE=E3=81=A8=E5=90=8C=E3=81=98=E3=81=AB=E3=81=99?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2283: 第五階層のShortageについて数値をlicensesummaryのものと同じにする](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2283) 以下のプルリクで受けた指摘点を修正しました。 https://dev.azure.com/ODMSCloud/ODMS%20Cloud/_git/ODMS%20Cloud/pullrequest/273 具体的な修正内容は以下になります。 ・子アカウントの数分だけDBクエリしにいく問題を修正 ・有効期限の閾値となる日時を算出するclassを追加 ## レビューポイント ご指摘いただいた問題がこの対応で解消されているか。 ## UIの変更 なし ## 動作確認状況 ローカルで動作確認済み ## 補足 なし --- .../accounts/accounts.service.spec.ts | 148 +++++++++++++++++- .../src/features/accounts/accounts.service.ts | 38 ++--- .../src/features/accounts/test/utility.ts | 24 +++ .../src/features/accounts/types/types.ts | 10 +- .../src/features/licenses/types/types.ts | 14 ++ .../accounts/accounts.repository.service.ts | 73 ++++----- 6 files changed, 236 insertions(+), 71 deletions(-) diff --git a/dictation_server/src/features/accounts/accounts.service.spec.ts b/dictation_server/src/features/accounts/accounts.service.spec.ts index c4925e8..7ef47f1 100644 --- a/dictation_server/src/features/accounts/accounts.service.spec.ts +++ b/dictation_server/src/features/accounts/accounts.service.spec.ts @@ -14,6 +14,7 @@ import { createAccount, createLicense, createLicenseOrder, + createLicenseSetExpiryDateAndStatus, } from './test/utility'; import { DataSource } from 'typeorm'; import { makeTestingModule } from '../../common/test/modules'; @@ -346,7 +347,7 @@ const expectedAccountLisenceCounts = { isStorageAvailable: false, }; -describe('createPartnerAccount', () => { +describe('getPartnerAccount', () => { let source: DataSource = null; beforeEach(async () => { source = new DataSource({ @@ -387,6 +388,20 @@ describe('createPartnerAccount', () => { 'CHILDCORP2', ); + // 第二にリクエストを投げる用の第三を作成 + const { accountId: childAccountId3 } = await createAccount( + source, + childAccountId1, + 3, + 'CHILDCORP3', + ); + const { accountId: childAccountId4 } = await createAccount( + source, + childAccountId2, + 3, + 'CHILDCORP4', + ); + // 所有ライセンスを追加(親:3、子1:1、子2:2) await createLicense(source, parentAccountId); await createLicense(source, parentAccountId); @@ -407,6 +422,10 @@ describe('createPartnerAccount', () => { ); await createLicenseOrder(source, childAccountId2, parentAccountId, 5); + // ライセンス注文を追加(子3→子1:10ライセンス、子4→子2:10ライセンス) + await createLicenseOrder(source, childAccountId3, childAccountId1, 10); + await createLicenseOrder(source, childAccountId4, childAccountId2, 10); + const service = module.get(AccountsService); const accountId = parentAccountId; const offset = 0; @@ -426,14 +445,139 @@ describe('createPartnerAccount', () => { expect(response.childrenPartnerLicenses[0].tier).toBe(2); expect(response.childrenPartnerLicenses[0].stockLicense).toBe(1); expect(response.childrenPartnerLicenses[0].issueRequesting).toBe(10); + expect(response.childrenPartnerLicenses[0].shortage).toBe(9); expect(response.childrenPartnerLicenses[1].companyName).toBe('CHILDCORP2'); expect(response.childrenPartnerLicenses[1].tier).toBe(2); expect(response.childrenPartnerLicenses[1].stockLicense).toBe(2); - expect(response.childrenPartnerLicenses[1].issueRequesting).toBe(5); + expect(response.childrenPartnerLicenses[1].shortage).toBe(8); }); }); +describe('getPartnerAccount', () => { + let source: DataSource = null; + beforeEach(async () => { + source = new DataSource({ + type: 'sqlite', + database: ':memory:', + logging: false, + entities: [__dirname + '/../../**/*.entity{.ts,.js}'], + synchronize: true, // trueにすると自動的にmigrationが行われるため注意 + }); + return source.initialize(); + }); + + afterEach(async () => { + await source.destroy(); + source = null; + }); + + it('パラメータのアカウント自身と子アカウントに紐つくライセンス情報を取得する(第五のshortage確認)', async () => { + const module = await makeTestingModule(source); + + // 親アカウントと子アカウント2つ作成 + const { accountId: parentAccountId } = await createAccount( + source, + 0, + 4, + 'PARENTCORP', + ); + const { accountId: childAccountId1 } = await createAccount( + source, + parentAccountId, + 5, + 'CHILDCORP1', + ); + const { accountId: childAccountId2 } = await createAccount( + source, + parentAccountId, + 5, + 'CHILDCORP2', + ); + const { accountId: childAccountId3 } = await createAccount( + source, + parentAccountId, + 5, + 'CHILDCORP3', + ); + + // 有効期限が14日後のライセンスを追加(5ライセンス) + let expiryDate = new Date(); + expiryDate.setDate(expiryDate.getDate() + 14); + expiryDate.setHours(23, 59, 59, 999); + for (let i = 0; i < 5; i++) { + await createLicenseSetExpiryDateAndStatus( + source, + childAccountId1, + expiryDate, + 'Allocated', + ); + await createLicenseSetExpiryDateAndStatus( + source, + childAccountId2, + expiryDate, + 'Allocated', + ); + } + + // 有効期限が15日後のライセンスを追加 + expiryDate.setDate(expiryDate.getDate() + 15); + expiryDate.setHours(23, 59, 59, 999); + await createLicenseSetExpiryDateAndStatus( + source, + childAccountId3, + expiryDate, + 'Allocated', + ); + + // 有効期限が迫っていないライセンスを追加(子1:各ステータスのライセンスを1つずつ、計4つ) + const status = ['Unallocated', 'Allocated', 'Reusable', 'Deleted']; + status.forEach(async (element) => { + await createLicenseSetExpiryDateAndStatus( + source, + childAccountId1, + new Date(2500, 1, 1, 23, 59, 59), + element, + ); + }); + + // 有効期限が迫っていないライセンスを追加(子2:Unallocatedを10件) + for (let i = 0; i < 10; i++) { + await createLicenseSetExpiryDateAndStatus( + source, + childAccountId2, + new Date(2500, 1, 1, 23, 59, 59), + 'Unallocated', + ); + } + + // 有効期限未設定のライセンスを1件追加(子1) + await createLicense(source, childAccountId1); + + const service = module.get(AccountsService); + const accountId = parentAccountId; + const offset = 0; + const limit = 20; + + const response = await service.getPartnerLicenses(limit, offset, accountId); + + // 有効期限間近(5件)+ 有効期限間近でない(3件)'Unallocated', 'Allocated', 'Reusable'+ 有効期限未設定(1件) → 9件 + expect(response.childrenPartnerLicenses[0].stockLicense).toBe(9); + // 有効期限間近(5件) - {有効期限間近でない未割当(2件)'Unallocated', 'Reusable'+ 有効期限未設定(1件)} → 2件 + expect(response.childrenPartnerLicenses[0].shortage).toBe(2); + + // 有効期限間近(5件)+ 有効期限間近でない(10件) → 15件 + expect(response.childrenPartnerLicenses[1].stockLicense).toBe(15); + // 有効期限間近(5件)- (有効期限間近でない未割当(10件)'Unallocated' + 有効期限未設定(1件)) → -5件 → 0件 + expect(response.childrenPartnerLicenses[1].shortage).toBe(0); + + expect(response.childrenPartnerLicenses[2].stockLicense).toBe(1); + // 有効期限が15日後のものはshortageにカウントされない + expect(response.childrenPartnerLicenses[2].shortage).toBe(0); + }); +}); + + describe('getOrderHistories', () => { let source: DataSource = null; beforeEach(async () => { diff --git a/dictation_server/src/features/accounts/accounts.service.ts b/dictation_server/src/features/accounts/accounts.service.ts index 9995b94..8db4527 100644 --- a/dictation_server/src/features/accounts/accounts.service.ts +++ b/dictation_server/src/features/accounts/accounts.service.ts @@ -26,7 +26,10 @@ import { Dealer, GetMyAccountResponse, } from './types/types'; -import { DateWithZeroTime } from '../licenses/types/types'; +import { + DateWithZeroTime, + ExpirationThresholdDate, +} from '../licenses/types/types'; import { GetLicenseSummaryResponse, Typist } from './types/types'; import { AccessToken } from '../../common/token'; import { UserNotFoundError } from '../../repositories/users/errors/types'; @@ -58,13 +61,9 @@ export class AccountsService { try { const currentDate = new DateWithZeroTime(); - - const expiringSoonDate = new Date(currentDate.getTime()); - expiringSoonDate.setDate( - currentDate.getDate() + LICENSE_EXPIRATION_THRESHOLD_DAYS, + const expiringSoonDate = new ExpirationThresholdDate( + currentDate.getTime(), ); - // システム上有効期限日付の23時59分59秒999ミリ秒までライセンスは有効なため、各値最大値をセット - expiringSoonDate.setHours(23, 59, 59, 999); const { licenseSummary, isStorageAvailable } = await this.accountRepository.getLicenseSummaryInfo( @@ -446,11 +445,17 @@ export class AccountsService { try { const currentDate = new DateWithZeroTime(); + // 第五階層のshortage算出に使用する日付情報 + // 「有効期限が現在日付からしきい値以内のライセンス数」を取得するため、しきい値となる日付を作成する + const expiringSoonDate = new ExpirationThresholdDate( + currentDate.getTime(), + ); const getPartnerLicenseResult = await this.accountRepository.getPartnerLicense( accountId, currentDate, + expiringSoonDate, offset, limit, ); @@ -471,27 +476,14 @@ export class AccountsService { }, ); - // 第五階層のshortage算出に使用する日付情報 - // 「有効期限が現在日付からしきい値以内のライセンス数」を取得するため、しきい値となる日付を作成する - const expiringSoonDate = new Date(currentDate.getTime()); - expiringSoonDate.setDate( - currentDate.getDate() + LICENSE_EXPIRATION_THRESHOLD_DAYS, - ); - expiringSoonDate.setHours(23, 59, 59, 999); - // 各子アカウントのShortageを算出してreturn用の変数にマージする const childrenPartnerLicenses: PartnerLicenseInfo[] = []; for (const childPartnerLicenseFromRepository of getPartnerLicenseResult.childPartnerLicensesFromRepository) { let childShortage; if (childPartnerLicenseFromRepository.tier === TIERS.TIER5) { - // 第五階層の場合計算式が異なるため、別途値を取得する - const { expiringSoonLicense, allocatableLicenseWithMargin } = - await this.accountRepository.getLicenseCountForShortage( - childPartnerLicenseFromRepository.accountId, - currentDate, - expiringSoonDate, - ); - childShortage = allocatableLicenseWithMargin - expiringSoonLicense; + childShortage = + childPartnerLicenseFromRepository.allocatableLicenseWithMargin - + childPartnerLicenseFromRepository.expiringSoonLicense; } else { childShortage = childPartnerLicenseFromRepository.stockLicense - diff --git a/dictation_server/src/features/accounts/test/utility.ts b/dictation_server/src/features/accounts/test/utility.ts index a098ee7..c570a4b 100644 --- a/dictation_server/src/features/accounts/test/utility.ts +++ b/dictation_server/src/features/accounts/test/utility.ts @@ -50,6 +50,30 @@ export const createLicense = async ( identifiers.pop() as License; }; +// 有効期限・ステータス付きのライセンスを作成 +export const createLicenseSetExpiryDateAndStatus = async ( + datasource: DataSource, + accountId: number, + expiryDate: Date, + status: string, +): Promise => { + const { identifiers } = await datasource.getRepository(License).insert({ + expiry_date: expiryDate, + account_id: accountId, + type: 'NORMAL', + status: status, + allocated_user_id: null, + order_id: null, + deleted_at: null, + delete_order_id: null, + created_by: 'test_runner', + created_at: new Date(), + updated_by: 'updater', + updated_at: new Date(), + }); + identifiers.pop() as License; +}; + export const createLicenseOrder = async ( datasource: DataSource, fromAccountId: number, diff --git a/dictation_server/src/features/accounts/types/types.ts b/dictation_server/src/features/accounts/types/types.ts index 0f4baf7..f62c9a0 100644 --- a/dictation_server/src/features/accounts/types/types.ts +++ b/dictation_server/src/features/accounts/types/types.ts @@ -189,11 +189,17 @@ export class GetPartnerLicensesResponse { childrenPartnerLicenses: PartnerLicenseInfo[]; } +// 第五階層のshortage算出用 +export class PartnerLicenseInfoForShortage { + expiringSoonLicense?:number; + allocatableLicenseWithMargin?:number; +} + // RepositoryからPartnerLicenseInfoに関する情報を取得する際の型 export type PartnerLicenseInfoForRepository = Omit< - PartnerLicenseInfo, + PartnerLicenseInfo & PartnerLicenseInfoForShortage, 'shortage' ->; + >; export class GetOrderHistoriesRequest { @ApiProperty({ description: '取得件数' }) diff --git a/dictation_server/src/features/licenses/types/types.ts b/dictation_server/src/features/licenses/types/types.ts index a4df377..ad6b933 100644 --- a/dictation_server/src/features/licenses/types/types.ts +++ b/dictation_server/src/features/licenses/types/types.ts @@ -1,5 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsInt, Matches, Max, Min, Length } from 'class-validator'; +import { LICENSE_EXPIRATION_THRESHOLD_DAYS } from '../../../constants'; export class CreateOrdersRequest { @ApiProperty() @@ -48,3 +49,16 @@ export class DateWithZeroTime extends Date { this.setHours(0, 0, 0, 0); // 時分秒を"0:00:00.000"に固定 } } + +// ライセンスの算出用に、閾値となる時刻(23:59:59.999)の日付を取得する +export class ExpirationThresholdDate extends Date { + constructor(...args: any[]) { + if (args.length === 0) { + super(); // 引数がない場合、現在の日付で初期化 + } else { + super(...(args as [string])); // 引数がある場合、引数をそのままDateクラスのコンストラクタに渡す + } + this.setDate(this.getDate() + LICENSE_EXPIRATION_THRESHOLD_DAYS); + this.setHours(23, 59, 59, 999); // 時分秒を"23:59:59.999"に固定 + } +} \ No newline at end of file diff --git a/dictation_server/src/repositories/accounts/accounts.repository.service.ts b/dictation_server/src/repositories/accounts/accounts.repository.service.ts index 2aabfff..f7ecd96 100644 --- a/dictation_server/src/repositories/accounts/accounts.repository.service.ts +++ b/dictation_server/src/repositories/accounts/accounts.repository.service.ts @@ -429,6 +429,7 @@ export class AccountsRepositoryService { async getPartnerLicense( id: number, currentDate: Date, + expiringSoonDate: Date, offset: number, limit: number, ): Promise<{ @@ -486,16 +487,36 @@ export class AccountsRepositoryService { entityManager, ); + // 第五の不足数を算出するためのライセンス数情報を取得する + let expiringSoonLicense: number; + let allocatableLicenseWithMargin: number; + if (childAccount.tier === TIERS.TIER5) { + expiringSoonLicense = await this.getExpiringSoonLicense( + entityManager, + childAccount.id, + currentDate, + expiringSoonDate, + ); + allocatableLicenseWithMargin = + await this.getAllocatableLicenseWithMargin( + entityManager, + childAccount.id, + expiringSoonDate, + ); + } + // 戻り値用の値を設定 const childPartnerLicenseFromRepository: PartnerLicenseInfoForRepository = - { - accountId: childAccount.id, - tier: childAccount.tier, - companyName: childAccount.company_name, - stockLicense: childLicenseOrderStatus.stockLicense, - issuedRequested: childLicenseOrderStatus.issuedRequested, - issueRequesting: childLicenseOrderStatus.issueRequesting, - }; + { + accountId: childAccount.id, + tier: childAccount.tier, + companyName: childAccount.company_name, + stockLicense: childLicenseOrderStatus.stockLicense, + issuedRequested: childLicenseOrderStatus.issuedRequested, + issueRequesting: childLicenseOrderStatus.issueRequesting, + expiringSoonLicense: expiringSoonLicense, + allocatableLicenseWithMargin: allocatableLicenseWithMargin, + }; childPartnerLicensesFromRepository.push( childPartnerLicenseFromRepository, @@ -517,42 +538,6 @@ export class AccountsRepositoryService { }); } - /** - * 不足数を算出するためのライセンス数情報を取得する - * @param id - * @param currentDate - * @param expiringSoonDate - * @returns expiringSoonLicense - * @returns expiringSoonDate - */ - async getLicenseCountForShortage( - id: number, - currentDate: Date, - expiringSoonDate: Date, - ): Promise<{ - expiringSoonLicense: number; - allocatableLicenseWithMargin: number; - }> { - return await this.dataSource.transaction(async (entityManager) => { - const expiringSoonLicense = await this.getExpiringSoonLicense( - entityManager, - id, - currentDate, - expiringSoonDate, - ); - const allocatableLicenseWithMargin = - await this.getAllocatableLicenseWithMargin( - entityManager, - id, - expiringSoonDate, - ); - return { - expiringSoonLicense: expiringSoonLicense, - allocatableLicenseWithMargin: allocatableLicenseWithMargin, - }; - }); - } - /** * Dealer(Tier4)アカウント情報を取得する * @returns dealer accounts From ee783585aef8eba4774e539858848d779bf0b4f3 Mon Sep 17 00:00:00 2001 From: "saito.k" Date: Tue, 8 Aug 2023 09:43:04 +0000 Subject: [PATCH 03/40] =?UTF-8?q?Merged=20PR=20316:=20dev=E5=8B=95?= =?UTF-8?q?=E4=BD=9C=E7=A2=BA=E8=AA=8D=E3=81=A7=E7=99=BA=E8=A6=8B=E3=81=97?= =?UTF-8?q?=E3=81=9F=E4=BF=AE=E6=AD=A3=E5=AF=BE=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2368: dev動作確認で発見した修正対象](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2368) - デザイン周り - ラジオボタン・チェックボックスのラべルを押下しても状態を変更できるように修正 - 機能 - もともとパスワードがあったとき、フォーカスを当てて伏字がなくなった状態になった場合は、値をチェックするように修正 - Encryption:ON→パスワード入力→Encryption:OFFにしてユーザー追加したとき、入力していたパスワードがServerに送られてしまっていたので、Encryption:OFFにしたらパスワードをundefinedにするように修正 - ポップアップが閉じたときにユーザー一覧画面を更新する挙動になっていたため、閉じたときは何もしないように修正 ## レビューポイント - 修正箇所・修正対応に問題はないか - 確認しておいた方が良い挙動はあるか ## UIの変更 -https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task2368?csf=1&web=1&e=pyfaXd ## 動作確認状況 - ローカルで確認 ## 補足 - 相談、参考資料などがあれば --- .../src/features/user/operations.ts | 13 ++++++----- .../src/features/user/selectors.ts | 7 ++---- .../src/features/user/userSlice.ts | 9 ++++++-- .../src/pages/UserListPage/index.tsx | 2 -- .../src/pages/UserListPage/popup.tsx | 23 +++++++++++-------- .../src/pages/UserListPage/updatePopup.tsx | 23 +++++++++++-------- 6 files changed, 44 insertions(+), 33 deletions(-) diff --git a/dictation_client/src/features/user/operations.ts b/dictation_client/src/features/user/operations.ts index 8c35726..d975075 100644 --- a/dictation_client/src/features/user/operations.ts +++ b/dictation_client/src/features/user/operations.ts @@ -59,16 +59,17 @@ export const addUserAsync = createAsyncThunk< const config = new Configuration(configuration); const usersApi = new UsersApi(config); const { addUser } = state.user.apps; + const newUser = { ...addUser }; // roleがAUTHOR以外の場合、不要なプロパティをundefinedにする - if (addUser.role !== USER_ROLES.AUTHOR) { - addUser.authorId = undefined; - addUser.encryption = undefined; - addUser.prompt = undefined; - addUser.encryptionPassword = undefined; + if (newUser.role !== USER_ROLES.AUTHOR) { + newUser.authorId = undefined; + newUser.encryption = undefined; + newUser.prompt = undefined; + newUser.encryptionPassword = undefined; } try { - await usersApi.signup(addUser, { + await usersApi.signup(newUser, { headers: { authorization: `Bearer ${accessToken}` }, }); thunkApi.dispatch( diff --git a/dictation_client/src/features/user/selectors.ts b/dictation_client/src/features/user/selectors.ts index 854e02b..5cb63be 100644 --- a/dictation_client/src/features/user/selectors.ts +++ b/dictation_client/src/features/user/selectors.ts @@ -63,11 +63,8 @@ export const selectUpdateValidationErrors = (state: RootState) => { ); if (passwordError) { - // 最初にEncryptionがfasleで、Encryptionがtrueに変更された場合、EncryptionPasswordが必須 - if (!initEncryption) { - hasErrorIncorrectEncryptionPassword = true; - // Encryptionがある状態で変更がある場合、EncryptionPasswordが空でもエラーにしない - } else if (!encryptionPassword || encryptionPassword === "") { + // 最初にEncryptionがtrueで、EncryptionPassword変更されていない場合はエラーとしない + if (initEncryption && encryptionPassword === undefined) { hasErrorIncorrectEncryptionPassword = false; } else { hasErrorIncorrectEncryptionPassword = true; diff --git a/dictation_client/src/features/user/userSlice.ts b/dictation_client/src/features/user/userSlice.ts index 46066d7..528ca61 100644 --- a/dictation_client/src/features/user/userSlice.ts +++ b/dictation_client/src/features/user/userSlice.ts @@ -90,6 +90,9 @@ export const userSlice = createSlice({ ) => { const { encryption } = action.payload; state.apps.addUser.encryption = encryption; + if (!encryption) { + state.apps.addUser.encryptionPassword = undefined; + } }, changePrompt: (state, action: PayloadAction<{ prompt: boolean }>) => { const { prompt } = action.payload; @@ -170,14 +173,16 @@ export const userSlice = createSlice({ if (initEncryption && encryption && !password) { state.apps.hasPasswordMask = true; } + if (!encryption) { + state.apps.updateUser.encryptionPassword = undefined; + } }, changeUpdateEncryptionPassword: ( state, action: PayloadAction<{ encryptionPassword: string }> ) => { const { encryptionPassword } = action.payload; - state.apps.updateUser.encryptionPassword = - encryptionPassword === "" ? undefined : encryptionPassword; + state.apps.updateUser.encryptionPassword = encryptionPassword; }, changeUpdatePrompt: (state, action: PayloadAction<{ prompt: boolean }>) => { const { prompt } = action.payload; diff --git a/dictation_client/src/pages/UserListPage/index.tsx b/dictation_client/src/pages/UserListPage/index.tsx index 787f902..0f4ac6b 100644 --- a/dictation_client/src/pages/UserListPage/index.tsx +++ b/dictation_client/src/pages/UserListPage/index.tsx @@ -59,14 +59,12 @@ const UserListPage: React.FC = (): JSX.Element => { isOpen={isUpdatePopupOpen} onClose={() => { setIsUpdatePopupOpen(false); - dispatch(listUsersAsync()); }} /> { setIsPopupOpen(false); - dispatch(listUsersAsync()); }} />
diff --git a/dictation_client/src/pages/UserListPage/popup.tsx b/dictation_client/src/pages/UserListPage/popup.tsx index 9e0328f..35c8f0f 100644 --- a/dictation_client/src/pages/UserListPage/popup.tsx +++ b/dictation_client/src/pages/UserListPage/popup.tsx @@ -20,6 +20,7 @@ import { changePrompt, changeEncryption, changeEncryptionPassword, + listUsersAsync, } from "features/user"; import { USER_ROLES } from "components/auth/constants"; import close from "../../assets/images/close.svg"; @@ -75,6 +76,7 @@ export const UserAddPopup: React.FC = (props) => { if (meta.requestStatus === "fulfilled") { closePopup(); + dispatch(listUsersAsync()); } }, [ hasErrorEmptyName, @@ -146,9 +148,9 @@ export const UserAddPopup: React.FC = (props) => {
{t(getTranslationID("userListPage.label.role"))}
-
{t(getTranslationID("userListPage.label.setting"))}

-

-

-

{t(getTranslationID("userListPage.label.role"))}
-
{t(getTranslationID("userListPage.label.setting"))}

-

-

-