OMDSCloud/dictation_function/src/functions/licenseAutoAllocation.ts
maruyama.t 0a9f125d76 Merged PR 668: 有効期限>翌日になっているのを>当日に修正する
## 概要
[Task3421: 有効期限>翌日になっているのを>当日に修正する](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3421)

- 有効期限>翌日になっているのを>当日に修正
翌日が有効期限のものは割り当ての対象とする。

念のためテストケースを追加

## レビューポイント
- とくになし

## 動作確認状況
- ローカルで確認

## 補足
- 相談、参考資料などがあれば
2023-12-28 04:27:19 +00:00

388 lines
13 KiB
TypeScript

import { app, InvocationContext, Timer } from "@azure/functions";
import { Between, DataSource, In, MoreThan, Repository } from "typeorm";
import { User } from "../entity/user.entity";
import { Account } from "../entity/account.entity";
import { License, LicenseAllocationHistory } from "../entity/license.entity";
import * as dotenv from "dotenv";
import {
LICENSE_ALLOCATED_STATUS,
LICENSE_TYPE,
SWITCH_FROM_TYPE,
TIERS,
USER_ROLES,
} from "../constants";
import {
DateWithDayEndTime,
DateWithZeroTime,
NewAllocatedLicenseExpirationDate,
} from "../common/types/types";
export async function licenseAutoAllocationProcessing(
context: InvocationContext,
datasource: DataSource,
dateToTrigger?: Date
): Promise<void> {
try {
context.log("[IN]licenseAutoAllocationProcessing");
// ライセンスの有効期間判定用
let currentDateZeroTime = new DateWithZeroTime();
let currentDateEndTime = new DateWithDayEndTime();
if (dateToTrigger) {
currentDateZeroTime = new DateWithZeroTime(dateToTrigger);
currentDateEndTime = new DateWithDayEndTime(dateToTrigger);
}
// 自動更新対象の候補となるアカウントを取得
const accountRepository = datasource.getRepository(Account);
const targetAccounts = await accountRepository.find({
where: {
tier: TIERS.TIER5,
},
});
// 自動更新対象となるアカウント・ユーザーを取得
const autoAllocationLists = await findTargetUser(
context,
datasource,
targetAccounts,
currentDateZeroTime,
currentDateEndTime
);
// 対象となるアカウント数分ループ
for (const autoAllocationList of autoAllocationLists) {
// ライセンスを割り当てる
await allocateLicense(
context,
datasource,
autoAllocationList,
currentDateZeroTime,
currentDateEndTime
);
}
} catch (e) {
context.log("licenseAutoAllocationProcessing failed.");
context.error(e);
throw e;
} finally {
context.log("[OUT]licenseAutoAllocationProcessing");
}
}
export async function licenseAutoAllocation(
myTimer: Timer,
context: InvocationContext
): Promise<void> {
try {
context.log("[IN]licenseAutoAllocation");
dotenv.config({ path: ".env" });
dotenv.config({ path: ".env.local", override: true });
let datasource: DataSource;
try {
datasource = new DataSource({
type: "mysql",
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
entities: [User, Account, License, LicenseAllocationHistory],
});
await datasource.initialize();
} catch (e) {
context.log("database initialize failed.");
context.error(e);
throw e;
}
await licenseAutoAllocationProcessing(context, datasource);
} catch (e) {
context.log("licenseAutoAllocation failed.");
context.error(e);
throw e;
} finally {
context.log("[OUT]licenseAutoAllocation");
}
}
/**
* 自動更新対象のアカウント・ユーザーを取得する
* @param context
* @param datasource
* @param targetAccounts 自動更新対象候補のアカウント
* @param currentDateZeroTime
* @param currentDateEndTime
* @returns autoAllocationList[] 自動更新対象のアカウント・ユーザーのIDリスト
*/
export async function findTargetUser(
context: InvocationContext,
datasource: DataSource,
targetAccounts: Account[],
currentDateZeroTime: DateWithZeroTime,
currentDateEndTime: DateWithDayEndTime
): Promise<autoAllocationList[]> {
try {
context.log("[IN]findTargetUser");
const autoAllocationList = [] as autoAllocationList[];
// ライセンス期限が今日で自動更新対象のユーザーを取得
const userRepository = datasource.getRepository(User);
for (const account of targetAccounts) {
// Author→Typist→Noneの優先度で割り当てたいので、roleごとに個別で取得
const targetAuthorUsers = await userRepository.find({
where: {
account_id: account.id,
auto_renew: true,
role: USER_ROLES.AUTHOR,
license: {
expiry_date: Between(currentDateZeroTime, currentDateEndTime),
},
},
relations: {
license: true,
},
});
const targetTypistUsers = await userRepository.find({
where: {
account_id: account.id,
auto_renew: true,
role: USER_ROLES.TYPIST,
license: {
expiry_date: Between(currentDateZeroTime, currentDateEndTime),
},
},
relations: {
license: true,
},
});
const targetNoneUsers = await userRepository.find({
where: {
account_id: account.id,
auto_renew: true,
role: USER_ROLES.NONE,
license: {
expiry_date: Between(currentDateZeroTime, currentDateEndTime),
},
},
relations: {
license: true,
},
});
// Author→Typist→Noneの順で配列に格納
const userIds = [] as number[];
for (const user of targetAuthorUsers) {
userIds.push(Number(user.id));
}
for (const user of targetTypistUsers) {
userIds.push(Number(user.id));
}
for (const user of targetNoneUsers) {
userIds.push(Number(user.id));
}
// 対象ユーザーが0件なら自動更新リストには含めない
if (userIds.length !== 0) {
autoAllocationList.push({
accountId: account.id,
userIds: userIds,
});
}
}
return autoAllocationList;
} catch (e) {
context.error(e);
context.log("findTargetUser failed.");
throw e;
} finally {
context.log("[OUT]findTargetUser");
}
}
/**
* 割り当て可能なライセンスを取得する
* @param context
* @param licenseRepository
* @param accountId 自動更新対象のアカウントID
* @returns License 割り当て可能なライセンス
*/
export async function getAutoAllocatableLicense(
context: InvocationContext,
licenseRepository: Repository<License>,
accountId: number,
currentDateEndTime: DateWithDayEndTime
): Promise<License | undefined> {
try {
context.log("[IN]getAutoAllocatableLicense");
// 割り当て可能なライセンスを取得
const license = await licenseRepository.findOne({
where: {
account_id: accountId,
status: In([
LICENSE_ALLOCATED_STATUS.REUSABLE,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
]),
expiry_date: MoreThan(currentDateEndTime) || null,
},
order: {
expiry_date: "ASC",
},
});
if (!license) {
// 割り当て可能なライセンスが存在しない場合でもエラーとはしたくないので、undifinedを返却する
return undefined;
}
return license;
} catch (e) {
context.error(e);
context.log("getAutoAllocatableLicense failed.");
throw e;
} finally {
context.log("[OUT]getAutoAllocatableLicense");
}
}
/**
* ライセンスを割り当てる
* @param context
* @param datasource
* @param account アカウント・ユーザーID
* @param currentDateZeroTime
* @param currentDateEndTime
*/
export async function allocateLicense(
context: InvocationContext,
datasource: DataSource,
autoAllocationList: autoAllocationList,
currentDateZeroTime: DateWithZeroTime,
currentDateEndTime: DateWithDayEndTime
): Promise<void> {
try {
context.log("[IN]allocateLicense");
// 自動更新対象ユーザーにライセンスを割り当てる
let hasAllocatebleLicense = true;
for (const userId of autoAllocationList.userIds) {
await datasource.transaction(async (entityManager) => {
const licenseRepository = entityManager.getRepository(License);
const licenseAllocationHistoryRepo = entityManager.getRepository(
LicenseAllocationHistory
);
// 割り当て可能なライセンスを取得する(自動割り当て用)
const autoAllocatableLicense = await getAutoAllocatableLicense(
context,
licenseRepository,
autoAllocationList.accountId,
currentDateEndTime
);
// 割り当て可能なライセンスが存在しなければreturnし、その後ループ終了
if (!autoAllocatableLicense) {
context.log(`allocatable license not exist.`);
hasAllocatebleLicense = false;
return;
}
// ライセンスが直前で手動割り当てされていたら、自動割り当てしない
const allocatedLicense = await licenseRepository.findOne({
where: {
allocated_user_id: userId,
expiry_date: Between(currentDateZeroTime, currentDateEndTime),
},
});
if (!allocatedLicense) {
context.log(`skip auto allocation. userID:${userId}`);
return;
}
// 古いライセンスの割り当て解除
allocatedLicense.status = LICENSE_ALLOCATED_STATUS.REUSABLE;
allocatedLicense.allocated_user_id = null;
await licenseRepository.save(allocatedLicense);
// ライセンス割り当て履歴テーブルへ登録
const deallocationHistory = new LicenseAllocationHistory();
deallocationHistory.user_id = userId;
deallocationHistory.license_id = allocatedLicense.id;
deallocationHistory.account_id = autoAllocationList.accountId;
deallocationHistory.is_allocated = false;
deallocationHistory.executed_at = new Date();
deallocationHistory.switch_from_type = SWITCH_FROM_TYPE.NONE;
await licenseAllocationHistoryRepo.save(deallocationHistory);
// 新規ライセンス割り当て
autoAllocatableLicense.status = LICENSE_ALLOCATED_STATUS.ALLOCATED;
autoAllocatableLicense.allocated_user_id = userId;
// 有効期限が未設定なら365日後に設定
if (!autoAllocatableLicense.expiry_date) {
autoAllocatableLicense.expiry_date =
new NewAllocatedLicenseExpirationDate();
}
await licenseRepository.save(autoAllocatableLicense);
context.log(
`license allocated. userID:${userId}, licenseID:${autoAllocatableLicense.id}`
);
// ライセンス割り当て履歴テーブルを更新するための処理
// 直近割り当てたライセンス種別を取得
const oldLicenseType = await licenseAllocationHistoryRepo.findOne({
relations: {
license: true,
},
where: { user_id: userId, is_allocated: true },
order: { executed_at: "DESC" },
});
let switchFromType = "";
if (oldLicenseType && oldLicenseType.license) {
switch (oldLicenseType.license.type) {
case LICENSE_TYPE.CARD:
switchFromType = SWITCH_FROM_TYPE.CARD;
break;
case LICENSE_TYPE.TRIAL:
switchFromType = SWITCH_FROM_TYPE.TRIAL;
break;
default:
switchFromType = SWITCH_FROM_TYPE.NONE;
break;
}
} else {
switchFromType = SWITCH_FROM_TYPE.NONE;
}
// ライセンス割り当て履歴テーブルへ登録
const allocationHistory = new LicenseAllocationHistory();
allocationHistory.user_id = userId;
allocationHistory.license_id = autoAllocatableLicense.id;
allocationHistory.account_id = autoAllocationList.accountId;
allocationHistory.is_allocated = true;
allocationHistory.executed_at = new Date();
// TODO switchFromTypeの値については「PBI1234: 第一階層として、ライセンス数推移情報をCSV出力したい」で正式対応
allocationHistory.switch_from_type = switchFromType;
await licenseAllocationHistoryRepo.save(allocationHistory);
});
// 割り当て可能なライセンスが存在しなければループ終了
if (!hasAllocatebleLicense) {
break;
}
}
} catch (e) {
// エラーが発生しても次のアカウントへの処理は継続させるため、例外をthrowせずにreturnだけする
context.error(e);
context.log("allocateLicense failed.");
return;
} finally {
context.log("[OUT]allocateLicense");
}
}
app.timer("licenseAutoAllocation", {
schedule: "0 0 16 * * *",
handler: licenseAutoAllocation,
});
class autoAllocationList {
accountId: number;
userIds: number[];
}