## 概要 [Task3421: 有効期限>翌日になっているのを>当日に修正する](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3421) - 有効期限>翌日になっているのを>当日に修正 翌日が有効期限のものは割り当ての対象とする。 念のためテストケースを追加 ## レビューポイント - とくになし ## 動作確認状況 - ローカルで確認 ## 補足 - 相談、参考資料などがあれば
388 lines
13 KiB
TypeScript
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[];
|
|
}
|