Merged PR 631: ライセンス自動割り当て処理実装(メイン処理)
## 概要 [Task3294: ライセンス自動割り当て処理実装(メイン処理)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3294) ライセンス自動割り当て処理を実装しました。 ラフスケッチでは1回のクエリでアカウント・ユーザーを両方取得する設計でしたが、実装難度・可読性の面から、 アカウントとユーザーを別々に取得するよう変更しています。 ## レビューポイント 処理内容に過不足がないか。 DBからのデータ取得時の条件に過不足がないか。 ## UIの変更 なし ## 動作確認状況 ローカルでUT,動作確認済み ## 補足 なし
This commit is contained in:
parent
9e1bc8944f
commit
32a452bdb2
@ -1,6 +1,7 @@
|
||||
import {
|
||||
LICENSE_EXPIRATION_DAYS,
|
||||
LICENSE_EXPIRATION_THRESHOLD_DAYS,
|
||||
LICENSE_EXPIRATION_TIME_WITH_TIMEZONE,
|
||||
TRIAL_LICENSE_EXPIRATION_DAYS,
|
||||
} from "../../constants";
|
||||
|
||||
@ -28,6 +29,19 @@ export class DateWithDayEndTime extends Date {
|
||||
}
|
||||
}
|
||||
|
||||
// 翌日の日付を取得する
|
||||
export class DateWithNextDayEndTime extends Date {
|
||||
constructor(...args: any[]) {
|
||||
if (args.length === 0) {
|
||||
super(); // 引数がない場合、現在の日付で初期化
|
||||
} else {
|
||||
super(...(args as [string])); // 引数がある場合、引数をそのままDateクラスのコンストラクタに渡す
|
||||
}
|
||||
this.setDate(this.getDate() + 1);
|
||||
this.setHours(23, 59, 59, 999); // 時分秒を"23:59:59.999"に固定
|
||||
}
|
||||
}
|
||||
|
||||
// ライセンスの算出用に、閾値となる時刻(23:59:59.999)の日付を取得する
|
||||
export class ExpirationThresholdDate extends Date {
|
||||
constructor(...args: any[]) {
|
||||
@ -49,6 +63,8 @@ export class NewTrialLicenseExpirationDate extends Date {
|
||||
} else {
|
||||
super(...(args as [string])); // 引数がある場合、引数をそのままDateクラスのコンストラクタに渡す
|
||||
}
|
||||
// タイムゾーンをカバーするために現在時刻に8時間を加算してから、30日後の日付を取得する
|
||||
this.setHours(this.getHours() + LICENSE_EXPIRATION_TIME_WITH_TIMEZONE);
|
||||
this.setDate(this.getDate() + TRIAL_LICENSE_EXPIRATION_DAYS);
|
||||
this.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
|
||||
this.setMilliseconds(0);
|
||||
@ -63,6 +79,8 @@ export class NewAllocatedLicenseExpirationDate extends Date {
|
||||
} else {
|
||||
super(...(args as [string])); // 引数がある場合、引数をそのままDateクラスのコンストラクタに渡す
|
||||
}
|
||||
// タイムゾーンをカバーするために現在時刻に8時間を加算してから、365日後の日付を取得する
|
||||
this.setHours(this.getHours() + LICENSE_EXPIRATION_TIME_WITH_TIMEZONE);
|
||||
this.setDate(this.getDate() + LICENSE_EXPIRATION_DAYS);
|
||||
this.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
|
||||
this.setMilliseconds(0);
|
||||
|
||||
@ -19,54 +19,54 @@ export const TIERS = {
|
||||
* 音声ファイルをEast USに保存する国リスト
|
||||
* @const {number}
|
||||
*/
|
||||
export const BLOB_STORAGE_REGION_US = ['CA', 'KY', 'US'];
|
||||
export const BLOB_STORAGE_REGION_US = ["CA", "KY", "US"];
|
||||
|
||||
/**
|
||||
* 音声ファイルをAustralia Eastに保存する国リスト
|
||||
* @const {number}
|
||||
*/
|
||||
export const BLOB_STORAGE_REGION_AU = ['AU', 'NZ'];
|
||||
export const BLOB_STORAGE_REGION_AU = ["AU", "NZ"];
|
||||
|
||||
/**
|
||||
* 音声ファイルをNorth Europeに保存する国リスト
|
||||
* @const {number}
|
||||
*/
|
||||
export const BLOB_STORAGE_REGION_EU = [
|
||||
'AT',
|
||||
'BE',
|
||||
'BG',
|
||||
'HR',
|
||||
'CY',
|
||||
'CZ',
|
||||
'DK',
|
||||
'EE',
|
||||
'FI',
|
||||
'FR',
|
||||
'DE',
|
||||
'GR',
|
||||
'HU',
|
||||
'IS',
|
||||
'IE',
|
||||
'IT',
|
||||
'LV',
|
||||
'LI',
|
||||
'LT',
|
||||
'LU',
|
||||
'MT',
|
||||
'NL',
|
||||
'NO',
|
||||
'PL',
|
||||
'PT',
|
||||
'RO',
|
||||
'RS',
|
||||
'SK',
|
||||
'SI',
|
||||
'ZA',
|
||||
'ES',
|
||||
'SE',
|
||||
'CH',
|
||||
'TR',
|
||||
'GB',
|
||||
"AT",
|
||||
"BE",
|
||||
"BG",
|
||||
"HR",
|
||||
"CY",
|
||||
"CZ",
|
||||
"DK",
|
||||
"EE",
|
||||
"FI",
|
||||
"FR",
|
||||
"DE",
|
||||
"GR",
|
||||
"HU",
|
||||
"IS",
|
||||
"IE",
|
||||
"IT",
|
||||
"LV",
|
||||
"LI",
|
||||
"LT",
|
||||
"LU",
|
||||
"MT",
|
||||
"NL",
|
||||
"NO",
|
||||
"PL",
|
||||
"PT",
|
||||
"RO",
|
||||
"RS",
|
||||
"SK",
|
||||
"SI",
|
||||
"ZA",
|
||||
"ES",
|
||||
"SE",
|
||||
"CH",
|
||||
"TR",
|
||||
"GB",
|
||||
];
|
||||
|
||||
/**
|
||||
@ -74,8 +74,8 @@ export const BLOB_STORAGE_REGION_EU = [
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const ADMIN_ROLES = {
|
||||
ADMIN: 'admin',
|
||||
STANDARD: 'standard',
|
||||
ADMIN: "admin",
|
||||
STANDARD: "standard",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
@ -83,9 +83,9 @@ export const ADMIN_ROLES = {
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const USER_ROLES = {
|
||||
NONE: 'none',
|
||||
AUTHOR: 'author',
|
||||
TYPIST: 'typist',
|
||||
NONE: "none",
|
||||
AUTHOR: "author",
|
||||
TYPIST: "typist",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
@ -93,9 +93,9 @@ export const USER_ROLES = {
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const LICENSE_ISSUE_STATUS = {
|
||||
ISSUE_REQUESTING: 'Issue Requesting',
|
||||
ISSUED: 'Issued',
|
||||
CANCELED: 'Order Canceled',
|
||||
ISSUE_REQUESTING: "Issue Requesting",
|
||||
ISSUED: "Issued",
|
||||
CANCELED: "Order Canceled",
|
||||
};
|
||||
|
||||
/**
|
||||
@ -103,28 +103,28 @@ export const LICENSE_ISSUE_STATUS = {
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const LICENSE_TYPE = {
|
||||
TRIAL: 'TRIAL',
|
||||
NORMAL: 'NORMAL',
|
||||
CARD: 'CARD',
|
||||
TRIAL: "TRIAL",
|
||||
NORMAL: "NORMAL",
|
||||
CARD: "CARD",
|
||||
} as const;
|
||||
/**
|
||||
* ライセンス状態
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const LICENSE_ALLOCATED_STATUS = {
|
||||
UNALLOCATED: 'Unallocated',
|
||||
ALLOCATED: 'Allocated',
|
||||
REUSABLE: 'Reusable',
|
||||
DELETED: 'Deleted',
|
||||
UNALLOCATED: "Unallocated",
|
||||
ALLOCATED: "Allocated",
|
||||
REUSABLE: "Reusable",
|
||||
DELETED: "Deleted",
|
||||
} as const;
|
||||
/**
|
||||
* 切り替え元種別
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const SWITCH_FROM_TYPE = {
|
||||
NONE: 'NONE',
|
||||
CARD: 'CARD',
|
||||
TRIAL: 'TRIAL',
|
||||
NONE: "NONE",
|
||||
CARD: "CARD",
|
||||
TRIAL: "TRIAL",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
@ -139,6 +139,12 @@ export const LICENSE_EXPIRATION_THRESHOLD_DAYS = 14;
|
||||
*/
|
||||
export const LICENSE_EXPIRATION_DAYS = 365;
|
||||
|
||||
/**
|
||||
* タイムゾーンを加味したライセンスの有効期間(8時間)
|
||||
* @const {number}
|
||||
*/
|
||||
export const LICENSE_EXPIRATION_TIME_WITH_TIMEZONE = 8;
|
||||
|
||||
/**
|
||||
* カードライセンスの桁数
|
||||
* @const {number}
|
||||
@ -156,36 +162,36 @@ export const OPTION_ITEM_NUM = 10;
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const TASK_STATUS = {
|
||||
UPLOADED: 'Uploaded',
|
||||
PENDING: 'Pending',
|
||||
IN_PROGRESS: 'InProgress',
|
||||
FINISHED: 'Finished',
|
||||
BACKUP: 'Backup',
|
||||
UPLOADED: "Uploaded",
|
||||
PENDING: "Pending",
|
||||
IN_PROGRESS: "InProgress",
|
||||
FINISHED: "Finished",
|
||||
BACKUP: "Backup",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* タスク一覧でソート可能な属性の一覧
|
||||
*/
|
||||
export const TASK_LIST_SORTABLE_ATTRIBUTES = [
|
||||
'JOB_NUMBER',
|
||||
'STATUS',
|
||||
'ENCRYPTION',
|
||||
'AUTHOR_ID',
|
||||
'WORK_TYPE',
|
||||
'FILE_NAME',
|
||||
'FILE_LENGTH',
|
||||
'FILE_SIZE',
|
||||
'RECORDING_STARTED_DATE',
|
||||
'RECORDING_FINISHED_DATE',
|
||||
'UPLOAD_DATE',
|
||||
'TRANSCRIPTION_STARTED_DATE',
|
||||
'TRANSCRIPTION_FINISHED_DATE',
|
||||
"JOB_NUMBER",
|
||||
"STATUS",
|
||||
"ENCRYPTION",
|
||||
"AUTHOR_ID",
|
||||
"WORK_TYPE",
|
||||
"FILE_NAME",
|
||||
"FILE_LENGTH",
|
||||
"FILE_SIZE",
|
||||
"RECORDING_STARTED_DATE",
|
||||
"RECORDING_FINISHED_DATE",
|
||||
"UPLOAD_DATE",
|
||||
"TRANSCRIPTION_STARTED_DATE",
|
||||
"TRANSCRIPTION_FINISHED_DATE",
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* タスク一覧のソート条件(昇順・降順)
|
||||
*/
|
||||
export const SORT_DIRECTIONS = ['ASC', 'DESC'] as const;
|
||||
export const SORT_DIRECTIONS = ["ASC", "DESC"] as const;
|
||||
|
||||
/**
|
||||
* 通知タグの最大個数
|
||||
@ -198,18 +204,18 @@ export const TAG_MAX_COUNT = 20;
|
||||
* 通知のプラットフォーム種別文字列
|
||||
*/
|
||||
export const PNS = {
|
||||
WNS: 'wns',
|
||||
APNS: 'apns',
|
||||
WNS: "wns",
|
||||
APNS: "apns",
|
||||
};
|
||||
|
||||
/**
|
||||
* ユーザーのライセンス状態
|
||||
*/
|
||||
export const USER_LICENSE_STATUS = {
|
||||
NORMAL: 'Normal',
|
||||
NO_LICENSE: 'NoLicense',
|
||||
ALERT: 'Alert',
|
||||
RENEW: 'Renew',
|
||||
NORMAL: "Normal",
|
||||
NO_LICENSE: "NoLicense",
|
||||
ALERT: "Alert",
|
||||
RENEW: "Renew",
|
||||
};
|
||||
|
||||
/**
|
||||
@ -234,9 +240,9 @@ export const WORKTYPE_MAX_COUNT = 20;
|
||||
* worktypeのDefault値の取りうる値
|
||||
**/
|
||||
export const OPTION_ITEM_VALUE_TYPE = {
|
||||
DEFAULT: 'Default',
|
||||
BLANK: 'Blank',
|
||||
LAST_INPUT: 'LastInput',
|
||||
DEFAULT: "Default",
|
||||
BLANK: "Blank",
|
||||
LAST_INPUT: "LastInput",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
@ -244,20 +250,20 @@ export const OPTION_ITEM_VALUE_TYPE = {
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const ADB2C_SIGN_IN_TYPE = {
|
||||
EMAILADDRESS: 'emailAddress',
|
||||
EMAILADDRESS: "emailAddress",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* MANUAL_RECOVERY_REQUIRED
|
||||
* @const {string}
|
||||
*/
|
||||
export const MANUAL_RECOVERY_REQUIRED = '[MANUAL_RECOVERY_REQUIRED]';
|
||||
export const MANUAL_RECOVERY_REQUIRED = "[MANUAL_RECOVERY_REQUIRED]";
|
||||
|
||||
/**
|
||||
* 利用規約種別
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const TERM_TYPE = {
|
||||
EULA: 'EULA',
|
||||
DPA: 'DPA',
|
||||
EULA: "EULA",
|
||||
DPA: "DPA",
|
||||
} as const;
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
UpdateDateColumn,
|
||||
OneToOne,
|
||||
JoinColumn,
|
||||
OneToMany,
|
||||
} from "typeorm";
|
||||
|
||||
@Entity({ name: "accounts" })
|
||||
@ -73,4 +74,7 @@ export class Account {
|
||||
@OneToOne(() => User, (user) => user.id)
|
||||
@JoinColumn({ name: "secondary_admin_user_id" })
|
||||
secondaryAdminUser: User | null;
|
||||
|
||||
@OneToMany(() => User, (user) => user.id)
|
||||
user: User[] | null;
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
UpdateDateColumn,
|
||||
JoinColumn,
|
||||
OneToOne,
|
||||
ManyToOne,
|
||||
} from "typeorm";
|
||||
import { bigintTransformer } from "../common/entity";
|
||||
import { User } from "./user.entity";
|
||||
@ -61,3 +62,54 @@ export class License {
|
||||
@JoinColumn({ name: "allocated_user_id" })
|
||||
user: User | null;
|
||||
}
|
||||
|
||||
@Entity({ name: "license_allocation_history" })
|
||||
export class LicenseAllocationHistory {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
user_id: number;
|
||||
|
||||
@Column()
|
||||
license_id: number;
|
||||
|
||||
@Column()
|
||||
is_allocated: boolean;
|
||||
|
||||
@Column()
|
||||
account_id: number;
|
||||
|
||||
@Column()
|
||||
executed_at: Date;
|
||||
|
||||
@Column()
|
||||
switch_from_type: string;
|
||||
|
||||
@Column({ nullable: true, type: "datetime" })
|
||||
deleted_at: Date | null;
|
||||
|
||||
@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",
|
||||
})
|
||||
updated_at: Date;
|
||||
|
||||
@ManyToOne(() => License, (licenses) => licenses.id, {
|
||||
createForeignKeyConstraints: false,
|
||||
}) // createForeignKeyConstraintsはSQLite用設定値.本番用は別途migrationで設定
|
||||
@JoinColumn({ name: "license_id" })
|
||||
license: License | null;
|
||||
}
|
||||
|
||||
@ -5,8 +5,11 @@ import {
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
OneToOne,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
} from "typeorm";
|
||||
import { License } from "./license.entity";
|
||||
import { Account } from "./account.entity";
|
||||
|
||||
@Entity({ name: "users" })
|
||||
export class User {
|
||||
@ -73,6 +76,12 @@ export class User {
|
||||
}) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
updated_at: Date;
|
||||
|
||||
@ManyToOne(() => Account, (account) => account.user, {
|
||||
createForeignKeyConstraints: false,
|
||||
}) // createForeignKeyConstraintsはSQLite用設定値.本番用は別途migrationで設定
|
||||
@JoinColumn({ name: "account_id" })
|
||||
account: Account | null;
|
||||
|
||||
@OneToOne(() => License, (license) => license.user)
|
||||
license: License | null;
|
||||
}
|
||||
|
||||
380
dictation_function/src/functions/licenseAutoAllocation.ts
Normal file
380
dictation_function/src/functions/licenseAutoAllocation.ts
Normal file
@ -0,0 +1,380 @@
|
||||
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,
|
||||
DateWithNextDayEndTime,
|
||||
DateWithZeroTime,
|
||||
NewAllocatedLicenseExpirationDate,
|
||||
} from "../common/types/types";
|
||||
|
||||
export async function licenseAutoAllocationProcessing(
|
||||
context: InvocationContext,
|
||||
datasource: DataSource
|
||||
): Promise<void> {
|
||||
try {
|
||||
context.log("[IN]licenseAutoAllocationProcessing");
|
||||
|
||||
// ライセンスの有効期間判定用
|
||||
const currentDateZeroTime = new DateWithZeroTime();
|
||||
const currentDateEndTime = new DateWithDayEndTime();
|
||||
|
||||
// 自動更新対象の候補となるアカウントを取得
|
||||
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
|
||||
): Promise<License | undefined> {
|
||||
try {
|
||||
const currentNextDateTime = new DateWithNextDayEndTime();
|
||||
// 割り当て可能なライセンスを取得
|
||||
const license = await licenseRepository.findOne({
|
||||
where: {
|
||||
account_id: accountId,
|
||||
status: In([
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
]),
|
||||
expiry_date: MoreThan(currentNextDateTime),
|
||||
},
|
||||
order: {
|
||||
expiry_date: "ASC",
|
||||
},
|
||||
});
|
||||
if (!license) {
|
||||
// 割り当て可能なライセンスが存在しない場合でもエラーとはしたくないので、undifinedを返却する
|
||||
return undefined;
|
||||
}
|
||||
return license;
|
||||
} catch (e) {
|
||||
context.error(e);
|
||||
context.log("getAutoAllocatableLicense failed.");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ライセンスを割り当てる
|
||||
* @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
|
||||
);
|
||||
|
||||
// 割り当て可能なライセンスが存在しなければ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[];
|
||||
}
|
||||
@ -3,7 +3,7 @@ import { DataSource } from "typeorm";
|
||||
import { User } from "../../entity/user.entity";
|
||||
import { Account } from "../../entity/account.entity";
|
||||
import { ADMIN_ROLES, USER_ROLES } from "../../constants";
|
||||
import { License } from "../../entity/license.entity";
|
||||
import { License, LicenseAllocationHistory } from "../../entity/license.entity";
|
||||
|
||||
type InitialTestDBState = {
|
||||
tier1Accounts: { account: Account; users: User[] }[];
|
||||
@ -196,3 +196,34 @@ export const createLicense = async (
|
||||
});
|
||||
identifiers.pop() as License;
|
||||
};
|
||||
|
||||
export const selectLicenseByAllocatedUser = async (
|
||||
datasource: DataSource,
|
||||
userId: number
|
||||
): Promise<{ license: License | null }> => {
|
||||
const license = await datasource.getRepository(License).findOne({
|
||||
where: {
|
||||
allocated_user_id: userId,
|
||||
},
|
||||
});
|
||||
return { license };
|
||||
};
|
||||
|
||||
export const selectLicenseAllocationHistory = async (
|
||||
datasource: DataSource,
|
||||
userId: number,
|
||||
licence_id: number
|
||||
): Promise<{ licenseAllocationHistory: LicenseAllocationHistory | null }> => {
|
||||
const licenseAllocationHistory = await datasource
|
||||
.getRepository(LicenseAllocationHistory)
|
||||
.findOne({
|
||||
where: {
|
||||
user_id: userId,
|
||||
license_id: licence_id,
|
||||
},
|
||||
order: {
|
||||
executed_at: "DESC",
|
||||
},
|
||||
});
|
||||
return { licenseAllocationHistory };
|
||||
};
|
||||
|
||||
425
dictation_function/src/test/licenseAutoAllocation.spec.ts
Normal file
425
dictation_function/src/test/licenseAutoAllocation.spec.ts
Normal file
@ -0,0 +1,425 @@
|
||||
import { DataSource } from "typeorm";
|
||||
import { licenseAutoAllocationProcessing } from "../functions/licenseAutoAllocation";
|
||||
import {
|
||||
ADMIN_ROLES,
|
||||
LICENSE_ALLOCATED_STATUS,
|
||||
LICENSE_TYPE,
|
||||
SWITCH_FROM_TYPE,
|
||||
TIERS,
|
||||
USER_ROLES,
|
||||
} from "../constants";
|
||||
import {
|
||||
DateWithDayEndTime,
|
||||
DateWithNextDayEndTime,
|
||||
DateWithZeroTime,
|
||||
ExpirationThresholdDate,
|
||||
NewAllocatedLicenseExpirationDate,
|
||||
} from "../common/types/types";
|
||||
import {
|
||||
makeTestAccount,
|
||||
createLicense,
|
||||
makeTestUser,
|
||||
selectLicenseByAllocatedUser,
|
||||
selectLicenseAllocationHistory,
|
||||
} from "./common/utility";
|
||||
import * as dotenv from "dotenv";
|
||||
import { InvocationContext } from "@azure/functions";
|
||||
|
||||
describe("licenseAlert", () => {
|
||||
dotenv.config({ path: ".env" });
|
||||
dotenv.config({ path: ".env.local", override: true });
|
||||
let source: DataSource | null = 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 () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it("有効期限が本日のライセンスが自動更新されること", async () => {
|
||||
if (!source) fail();
|
||||
const context = new InvocationContext();
|
||||
|
||||
const currentDateEndTime = new DateWithDayEndTime();
|
||||
console.log(currentDateEndTime);
|
||||
|
||||
// アカウント
|
||||
const account1 = await makeTestAccount(
|
||||
source,
|
||||
{ tier: 5 },
|
||||
{ role: `${USER_ROLES.NONE}` }
|
||||
);
|
||||
const account2 = await makeTestAccount(
|
||||
source,
|
||||
{ tier: 5 },
|
||||
{ role: `${USER_ROLES.NONE}` }
|
||||
);
|
||||
|
||||
// 更新対象のユーザー(3role分)
|
||||
const user1 = await makeTestUser(source, {
|
||||
account_id: account1.account.id,
|
||||
role: `${USER_ROLES.NONE}`,
|
||||
});
|
||||
const user2 = await makeTestUser(source, {
|
||||
account_id: account1.account.id,
|
||||
role: `${USER_ROLES.AUTHOR}`,
|
||||
});
|
||||
const user3 = await makeTestUser(source, {
|
||||
account_id: account1.account.id,
|
||||
role: `${USER_ROLES.TYPIST}`,
|
||||
});
|
||||
|
||||
// 更新対象ではないユーザー(まだ有効期限が残っている)
|
||||
const user4 = await makeTestUser(source, {
|
||||
account_id: account1.account.id,
|
||||
role: `${USER_ROLES.NONE}`,
|
||||
});
|
||||
|
||||
// 更新対象ではないユーザー(auto_renewがfalse)
|
||||
const user5 = await makeTestUser(source, {
|
||||
account_id: account1.account.id,
|
||||
role: `${USER_ROLES.NONE}`,
|
||||
auto_renew: false,
|
||||
});
|
||||
|
||||
// 割り当て済みで有効期限が本日のライセンス
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
currentDateEndTime,
|
||||
account1.account.id,
|
||||
LICENSE_TYPE.CARD,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
user1.id,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
2,
|
||||
currentDateEndTime,
|
||||
account1.account.id,
|
||||
LICENSE_TYPE.CARD,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
user2.id,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
3,
|
||||
currentDateEndTime,
|
||||
account1.account.id,
|
||||
LICENSE_TYPE.CARD,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
user3.id,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
20,
|
||||
currentDateEndTime,
|
||||
account2.account.id,
|
||||
LICENSE_TYPE.CARD,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
account2.admin.id,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
5,
|
||||
currentDateEndTime,
|
||||
account1.account.id,
|
||||
LICENSE_TYPE.CARD,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
user5.id,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
// 割り当て済みの更新対象ではないライセンス
|
||||
const nextDate = new Date();
|
||||
nextDate.setDate(nextDate.getDate() + 1);
|
||||
nextDate.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
|
||||
nextDate.setMilliseconds(0);
|
||||
await createLicense(
|
||||
source,
|
||||
4,
|
||||
nextDate,
|
||||
account1.account.id,
|
||||
LICENSE_TYPE.CARD,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
user4.id,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
// 有効期限が先の未割当ライセンスを作成
|
||||
// idが100,101のものは有効期限が当日、翌日なので自動割り当て対象外
|
||||
// idが102のものから割り当てられる
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + i);
|
||||
date.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
|
||||
date.setMilliseconds(0);
|
||||
await createLicense(
|
||||
source,
|
||||
i + 100,
|
||||
date,
|
||||
account1.account.id,
|
||||
LICENSE_TYPE.CARD,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + 30);
|
||||
date.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
|
||||
date.setMilliseconds(0);
|
||||
await createLicense(
|
||||
source,
|
||||
200,
|
||||
date,
|
||||
account2.account.id,
|
||||
LICENSE_TYPE.CARD,
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
await licenseAutoAllocationProcessing(context, source);
|
||||
const user1Allocated = await selectLicenseByAllocatedUser(source, user1.id);
|
||||
const user2Allocated = await selectLicenseByAllocatedUser(source, user2.id);
|
||||
const user3Allocated = await selectLicenseByAllocatedUser(source, user3.id);
|
||||
const user4Allocated = await selectLicenseByAllocatedUser(source, user4.id);
|
||||
const user5Allocated = await selectLicenseByAllocatedUser(source, user5.id);
|
||||
const admin2Allocated = await selectLicenseByAllocatedUser(
|
||||
source,
|
||||
account2.admin.id
|
||||
);
|
||||
const licenseAllocationHistory = await selectLicenseAllocationHistory(
|
||||
source,
|
||||
user1.id,
|
||||
104
|
||||
);
|
||||
// Author、Typist、Noneの優先順位で割り当てられていることを確認
|
||||
expect(user1Allocated.license?.id).toBe(104);
|
||||
expect(user2Allocated.license?.id).toBe(102);
|
||||
expect(user3Allocated.license?.id).toBe(103);
|
||||
// 有効期限がまだあるので、ライセンスが更新されていないことを確認
|
||||
expect(user4Allocated.license?.id).toBe(4);
|
||||
// auto_renewがfalseなので、ライセンスが更新されていないことを確認
|
||||
expect(user5Allocated.license?.id).toBe(5);
|
||||
// 複数アカウント分の処理が正常に行われていることの確認
|
||||
expect(admin2Allocated.license?.id).toBe(200);
|
||||
|
||||
// ライセンス割り当て履歴テーブルが更新されていることを確認
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
|
||||
user1.id
|
||||
);
|
||||
expect(
|
||||
licenseAllocationHistory.licenseAllocationHistory?.is_allocated
|
||||
).toBe(true);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
|
||||
account1.account.id
|
||||
);
|
||||
});
|
||||
|
||||
it("新たに割り当てられるライセンスが存在しないため、ライセンスが自動更新されない(エラーではない)", async () => {
|
||||
if (!source) fail();
|
||||
const context = new InvocationContext();
|
||||
|
||||
const currentDateEndTime = new DateWithDayEndTime();
|
||||
console.log(currentDateEndTime);
|
||||
|
||||
// アカウント
|
||||
const account1 = await makeTestAccount(
|
||||
source,
|
||||
{ tier: 5 },
|
||||
{ role: `${USER_ROLES.NONE}` }
|
||||
);
|
||||
|
||||
// 更新対象のユーザー(3role分)
|
||||
const user1 = await makeTestUser(source, {
|
||||
account_id: account1.account.id,
|
||||
role: `${USER_ROLES.NONE}`,
|
||||
});
|
||||
const user2 = await makeTestUser(source, {
|
||||
account_id: account1.account.id,
|
||||
role: `${USER_ROLES.AUTHOR}`,
|
||||
});
|
||||
const user3 = await makeTestUser(source, {
|
||||
account_id: account1.account.id,
|
||||
role: `${USER_ROLES.TYPIST}`,
|
||||
});
|
||||
|
||||
// 割り当て済みで有効期限が本日のライセンス
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
currentDateEndTime,
|
||||
account1.account.id,
|
||||
LICENSE_TYPE.CARD,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
user1.id,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
2,
|
||||
currentDateEndTime,
|
||||
account1.account.id,
|
||||
LICENSE_TYPE.CARD,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
user2.id,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
3,
|
||||
currentDateEndTime,
|
||||
account1.account.id,
|
||||
LICENSE_TYPE.CARD,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
user3.id,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
await licenseAutoAllocationProcessing(context, source);
|
||||
const user1Allocated = await selectLicenseByAllocatedUser(source, user1.id);
|
||||
const user2Allocated = await selectLicenseByAllocatedUser(source, user2.id);
|
||||
const user3Allocated = await selectLicenseByAllocatedUser(source, user3.id);
|
||||
// ライセンスが更新されていないことを確認
|
||||
expect(user1Allocated.license?.id).toBe(1);
|
||||
expect(user2Allocated.license?.id).toBe(2);
|
||||
expect(user3Allocated.license?.id).toBe(3);
|
||||
});
|
||||
|
||||
it("tier4のアカウントのため、ライセンスが自動更新されない", async () => {
|
||||
if (!source) fail();
|
||||
const context = new InvocationContext();
|
||||
|
||||
const currentDateEndTime = new DateWithDayEndTime();
|
||||
console.log(currentDateEndTime);
|
||||
|
||||
// アカウント
|
||||
const account1 = await makeTestAccount(
|
||||
source,
|
||||
{ tier: 4 },
|
||||
{ role: `${USER_ROLES.NONE}` }
|
||||
);
|
||||
|
||||
// 更新対象のユーザー(3role分)
|
||||
const user1 = await makeTestUser(source, {
|
||||
account_id: account1.account.id,
|
||||
role: `${USER_ROLES.NONE}`,
|
||||
});
|
||||
const user2 = await makeTestUser(source, {
|
||||
account_id: account1.account.id,
|
||||
role: `${USER_ROLES.AUTHOR}`,
|
||||
});
|
||||
const user3 = await makeTestUser(source, {
|
||||
account_id: account1.account.id,
|
||||
role: `${USER_ROLES.TYPIST}`,
|
||||
});
|
||||
|
||||
// 割り当て済みで有効期限が本日のライセンス
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
currentDateEndTime,
|
||||
account1.account.id,
|
||||
LICENSE_TYPE.CARD,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
user1.id,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
2,
|
||||
currentDateEndTime,
|
||||
account1.account.id,
|
||||
LICENSE_TYPE.CARD,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
user2.id,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
3,
|
||||
currentDateEndTime,
|
||||
account1.account.id,
|
||||
LICENSE_TYPE.CARD,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
user3.id,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
// 有効期限が先の未割当ライセンスを作成
|
||||
// idが100,101のものは有効期限が当日、翌日なので自動割り当て対象外
|
||||
// idが102のものから割り当てられる
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + i);
|
||||
date.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
|
||||
date.setMilliseconds(0);
|
||||
await createLicense(
|
||||
source,
|
||||
i + 100,
|
||||
date,
|
||||
account1.account.id,
|
||||
LICENSE_TYPE.CARD,
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
await licenseAutoAllocationProcessing(context, source);
|
||||
const user1Allocated = await selectLicenseByAllocatedUser(source, user1.id);
|
||||
const user2Allocated = await selectLicenseByAllocatedUser(source, user2.id);
|
||||
const user3Allocated = await selectLicenseByAllocatedUser(source, user3.id);
|
||||
// ライセンスが更新されていないことを確認
|
||||
expect(user1Allocated.license?.id).toBe(1);
|
||||
expect(user2Allocated.license?.id).toBe(2);
|
||||
expect(user3Allocated.license?.id).toBe(3);
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user