Merged PR 811: AzureFunctions実装1(DBから必要な情報を取得する)
## 概要 [Task3842: AzureFunctions実装1(DBから必要な情報を取得する)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3842) - ライセンス数推移情報CSV出力機能のAzureFuntion用関数を追加しました - DBから必要な情報を取得する処理を実装しました ## レビューポイント - 特にレビューしてほしい箇所 - DBアクセス時の結合・検索条件はラフスケッチの条件に対して過不足ないか - テストケースに不足はないか - 関数名、構造は分かりづらくないか ## UIの変更 - 無し ## クエリの変更 - 新規のため無し ## 動作確認状況 - ローカルで処理が正常終了することを確認 - ユニットテストが通ることを確認 - 行った修正がデグレを発生させていないことを確認できるか - Function全体のunittestを実施し通ることを確認 ## 補足 - 相談、参考資料などがあれば
This commit is contained in:
parent
ccc03da62d
commit
84fc89071a
@ -1,5 +1,6 @@
|
||||
import { bigintTransformer } from "../common/entity";
|
||||
import { User } from "./user.entity";
|
||||
import { User, UserArchive } from "./user.entity";
|
||||
import { License, LicenseArchive } from "./license.entity";
|
||||
import {
|
||||
Entity,
|
||||
Column,
|
||||
@ -75,6 +76,86 @@ export class Account {
|
||||
@JoinColumn({ name: "secondary_admin_user_id" })
|
||||
secondaryAdminUser: User | null;
|
||||
|
||||
@OneToMany(() => User, (user) => user.id)
|
||||
@OneToMany(() => User, (user) => user.account)
|
||||
@JoinColumn({ name: "id" })
|
||||
user: User[] | null;
|
||||
|
||||
@OneToMany(() => UserArchive, (userArchive) => userArchive.account)
|
||||
@JoinColumn({ name: "id" })
|
||||
userArchive: UserArchive[] | null;
|
||||
|
||||
@OneToMany(() => License, (license) => license.account)
|
||||
licenses: License[] | null;
|
||||
}
|
||||
|
||||
@Entity({ name: "accounts_archive" })
|
||||
export class AccountArchive {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ nullable: true, type: "bigint", transformer: bigintTransformer })
|
||||
parent_account_id: number | null;
|
||||
|
||||
@Column()
|
||||
tier: number;
|
||||
|
||||
@Column()
|
||||
country: string;
|
||||
|
||||
@Column({ default: false })
|
||||
delegation_permission: boolean;
|
||||
|
||||
@Column({ default: false })
|
||||
locked: boolean;
|
||||
|
||||
@Column({ default: false })
|
||||
verified: boolean;
|
||||
|
||||
@Column({ nullable: true, type: "bigint", transformer: bigintTransformer })
|
||||
primary_admin_user_id: number | null;
|
||||
|
||||
@Column({ nullable: true, type: "bigint", transformer: bigintTransformer })
|
||||
secondary_admin_user_id: number | null;
|
||||
|
||||
@Column({ nullable: true, type: "bigint", transformer: bigintTransformer })
|
||||
active_worktype_id: number | null;
|
||||
|
||||
@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",
|
||||
}) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
created_at: Date;
|
||||
|
||||
@Column({ nullable: true, type: "datetime" })
|
||||
updated_by: string | null;
|
||||
|
||||
@UpdateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: "datetime",
|
||||
}) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
updated_at: Date;
|
||||
|
||||
@OneToOne(() => UserArchive, (userArchive) => userArchive.id)
|
||||
@JoinColumn({ name: "primary_admin_user_id" })
|
||||
primaryAdminUser: UserArchive | null;
|
||||
|
||||
@OneToOne(() => UserArchive, (userArchive) => userArchive.id)
|
||||
@JoinColumn({ name: "secondary_admin_user_id" })
|
||||
secondaryAdminUser: UserArchive | null;
|
||||
|
||||
@OneToMany(() => UserArchive, (userArchive) => userArchive.account)
|
||||
@JoinColumn({ name: "id" })
|
||||
userArchive: UserArchive[] | null;
|
||||
|
||||
@OneToMany(
|
||||
() => LicenseArchive,
|
||||
(licenseArchive) => licenseArchive.accountArchive
|
||||
)
|
||||
licensesArchive: LicenseArchive[] | null;
|
||||
}
|
||||
|
||||
@ -9,8 +9,8 @@ import {
|
||||
ManyToOne,
|
||||
} from "typeorm";
|
||||
import { bigintTransformer } from "../common/entity";
|
||||
import { User } from "./user.entity";
|
||||
|
||||
import { User, UserArchive } from "./user.entity";
|
||||
import { Account, AccountArchive } from "./account.entity";
|
||||
@Entity({ name: "licenses" })
|
||||
export class License {
|
||||
@PrimaryGeneratedColumn()
|
||||
@ -61,6 +61,10 @@ export class License {
|
||||
@OneToOne(() => User, (user) => user.license)
|
||||
@JoinColumn({ name: "allocated_user_id" })
|
||||
user: User | null;
|
||||
|
||||
@ManyToOne(() => Account, (account) => account.licenses)
|
||||
@JoinColumn({ name: "account_id" })
|
||||
account: Account | null;
|
||||
}
|
||||
|
||||
@Entity({ name: "license_allocation_history" })
|
||||
@ -112,4 +116,125 @@ export class LicenseAllocationHistory {
|
||||
}) // createForeignKeyConstraintsはSQLite用設定値.本番用は別途migrationで設定
|
||||
@JoinColumn({ name: "license_id" })
|
||||
license: License | null;
|
||||
|
||||
@ManyToOne(() => User, (user) => user.licenseAllocationHistory) // Userエンティティとの関連を設定
|
||||
@JoinColumn({ name: "user_id" }) // user_idを外部キーとして使用
|
||||
user: User;
|
||||
}
|
||||
|
||||
@Entity({ name: "licenses_archive" })
|
||||
export class LicenseArchive {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ nullable: true, type: "datetime" })
|
||||
expiry_date: Date | null;
|
||||
|
||||
@Column()
|
||||
account_id: number;
|
||||
|
||||
@Column()
|
||||
type: string;
|
||||
|
||||
@Column()
|
||||
status: string;
|
||||
|
||||
@Column({ nullable: true, type: "bigint", transformer: bigintTransformer })
|
||||
allocated_user_id: number | null;
|
||||
|
||||
@Column({ nullable: true, type: "bigint", transformer: bigintTransformer })
|
||||
order_id: number | null;
|
||||
|
||||
@Column({ nullable: true, type: "datetime" })
|
||||
deleted_at: Date | null;
|
||||
|
||||
@Column({ nullable: true, type: "bigint", transformer: bigintTransformer })
|
||||
delete_order_id: number | 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;
|
||||
|
||||
@OneToOne(() => UserArchive, (userArchive) => userArchive.licenseArchive)
|
||||
@JoinColumn({ name: "allocated_user_id" })
|
||||
userArchive: UserArchive | null;
|
||||
|
||||
@ManyToOne(
|
||||
() => AccountArchive,
|
||||
(accountArchive) => accountArchive.licensesArchive
|
||||
)
|
||||
@JoinColumn({ name: "account_id" })
|
||||
accountArchive: AccountArchive | null;
|
||||
}
|
||||
|
||||
@Entity({ name: "license_allocation_history_archive" })
|
||||
export class LicenseAllocationHistoryArchive {
|
||||
@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(() => LicenseArchive, (licensesArchive) => licensesArchive.id, {
|
||||
createForeignKeyConstraints: false,
|
||||
}) // createForeignKeyConstraintsはSQLite用設定値.本番用は別途migrationで設定
|
||||
@JoinColumn({ name: "license_id" })
|
||||
license: LicenseArchive | null;
|
||||
|
||||
@ManyToOne(
|
||||
() => UserArchive,
|
||||
(userArchive) => userArchive.licenseAllocationHistoryArchive
|
||||
) // Userエンティティとの関連を設定
|
||||
@JoinColumn({ name: "user_id" }) // user_idを外部キーとして使用
|
||||
userArchive: UserArchive;
|
||||
}
|
||||
|
||||
@ -7,9 +7,10 @@ import {
|
||||
OneToOne,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
} from "typeorm";
|
||||
import { License } from "./license.entity";
|
||||
import { Account } from "./account.entity";
|
||||
import { License, LicenseAllocationHistory, LicenseArchive, LicenseAllocationHistoryArchive } from "./license.entity";
|
||||
import { Account, AccountArchive } from "./account.entity";
|
||||
|
||||
@Entity({ name: "users" })
|
||||
export class User {
|
||||
@ -46,9 +47,6 @@ export class User {
|
||||
@Column({ default: false })
|
||||
encryption: boolean;
|
||||
|
||||
@Column({ nullable: true, type: "varchar" })
|
||||
encryption_password: string | null;
|
||||
|
||||
@Column({ default: false })
|
||||
prompt: boolean;
|
||||
|
||||
@ -81,4 +79,104 @@ export class User {
|
||||
|
||||
@OneToOne(() => License, (license) => license.user)
|
||||
license: License | null;
|
||||
|
||||
@OneToMany(
|
||||
() => LicenseAllocationHistory,
|
||||
(licenseAllocationHistory) => licenseAllocationHistory.user
|
||||
)
|
||||
licenseAllocationHistory: LicenseAllocationHistory[] | null;
|
||||
}
|
||||
|
||||
|
||||
@Entity({ name: "users_archive" })
|
||||
export class UserArchive {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
external_id: string;
|
||||
|
||||
@Column()
|
||||
account_id: number;
|
||||
|
||||
@Column()
|
||||
role: string;
|
||||
|
||||
@Column({ nullable: true, type: "varchar" })
|
||||
author_id: string | null;
|
||||
|
||||
@Column({ nullable: true, type: "varchar" })
|
||||
accepted_eula_version: string | null;
|
||||
|
||||
@Column({ nullable: true, type: "varchar" })
|
||||
accepted_dpa_version: string | null;
|
||||
|
||||
@Column({ default: false })
|
||||
email_verified: boolean;
|
||||
|
||||
@Column({ default: true })
|
||||
auto_renew: boolean;
|
||||
|
||||
@Column({ default: true })
|
||||
notification: boolean;
|
||||
|
||||
@Column({ default: false })
|
||||
encryption: boolean;
|
||||
|
||||
@Column({ default: false })
|
||||
prompt: boolean;
|
||||
|
||||
@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",
|
||||
}) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
created_at: Date;
|
||||
|
||||
@Column({ nullable: true, type: "datetime" })
|
||||
updated_by: string | null;
|
||||
|
||||
@UpdateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: "datetime",
|
||||
}) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
updated_at: Date;
|
||||
|
||||
@ManyToOne(
|
||||
() => Account,
|
||||
(account) => account.userArchive,
|
||||
{
|
||||
createForeignKeyConstraints: false,
|
||||
}
|
||||
) // createForeignKeyConstraintsはSQLite用設定値.本番用は別途migrationで設定
|
||||
@JoinColumn({ name: "account_id" })
|
||||
account: Account | null;
|
||||
|
||||
@ManyToOne(
|
||||
() => AccountArchive,
|
||||
(accountArchive) => accountArchive.userArchive,
|
||||
{
|
||||
createForeignKeyConstraints: false,
|
||||
}
|
||||
) // createForeignKeyConstraintsはSQLite用設定値.本番用は別途migrationで設定
|
||||
@JoinColumn({ name: "account_id" })
|
||||
accountArchive: AccountArchive | null;
|
||||
|
||||
@OneToOne(
|
||||
() => LicenseArchive,
|
||||
(licenseArchive) => licenseArchive.userArchive
|
||||
)
|
||||
licenseArchive: LicenseArchive | null;
|
||||
|
||||
@OneToMany(
|
||||
() => LicenseAllocationHistoryArchive,
|
||||
(licenseAllocationHistoryArchive) =>
|
||||
licenseAllocationHistoryArchive.userArchive
|
||||
)
|
||||
licenseAllocationHistoryArchive: LicenseAllocationHistoryArchive[] | null;
|
||||
}
|
||||
|
||||
389
dictation_function/src/functions/analysisLicenses.ts
Normal file
389
dictation_function/src/functions/analysisLicenses.ts
Normal file
@ -0,0 +1,389 @@
|
||||
import { app, InvocationContext, Timer } from "@azure/functions";
|
||||
import { DataSource, Between } from "typeorm";
|
||||
import * as dotenv from "dotenv";
|
||||
import { User, UserArchive } from "../entity/user.entity";
|
||||
import { Account, AccountArchive } from "../entity/account.entity";
|
||||
import { License, LicenseAllocationHistory, LicenseArchive, LicenseAllocationHistoryArchive } from "../entity/license.entity";
|
||||
import { BlobstorageService } from "../blobstorage/blobstorage.service";
|
||||
import { LICENSE_ALLOCATED_STATUS, TIERS, SWITCH_FROM_TYPE } from "../constants";
|
||||
import { DateWithDayEndTime } from "../common/types/types";
|
||||
|
||||
/**
|
||||
* ライセンス数分析処理のメイン処理:ここから各処理を呼び出す
|
||||
* @param myTimer
|
||||
* @param context
|
||||
*/
|
||||
export async function analysisLicensesProcessing(
|
||||
context: InvocationContext,
|
||||
targetMonthYYYYMM: string,
|
||||
datasource: DataSource,
|
||||
blobstorageService: BlobstorageService,
|
||||
) {
|
||||
|
||||
try {
|
||||
context.log("[IN]analysisLicensesProcessing");
|
||||
|
||||
const baseData = await getBaseData(context, targetMonthYYYYMM, datasource);
|
||||
const baseDataFromDeletedAccounts = await getBaseDataFromDeletedAccounts(
|
||||
context,
|
||||
targetMonthYYYYMM,
|
||||
datasource
|
||||
);
|
||||
// TODO: 後続処理の呼び出しイメージ(別タスクで追加)
|
||||
// const outputCsvData = await transferData(context, baseData, baseDataFromDeletedAccounts);
|
||||
// await outputData(context, blobstorageService, outputCsvData);
|
||||
} catch (e) {
|
||||
context.log("analysisLicensesProcessing failed.");
|
||||
context.error(e);
|
||||
throw e;
|
||||
} finally {
|
||||
context.log("[OUT]analysisLicensesProcessing");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 集計元のデータをDBから取得する処理
|
||||
* @param context
|
||||
* @param targetMonthYYYYMM
|
||||
* @param datasource
|
||||
* 内部関数だがテスト用にexportする
|
||||
*/
|
||||
export async function getBaseData(
|
||||
context: InvocationContext,
|
||||
targetMonthYYYYMM: string,
|
||||
datasource: DataSource
|
||||
): Promise<BaseData> {
|
||||
try {
|
||||
context.log("[IN]getBaseData");
|
||||
|
||||
// 第五階層のアカウントとユーザーを取得する
|
||||
// 第五のアカウントを取得
|
||||
const accountRepository = datasource.getRepository(Account);
|
||||
const accountsAndUsersFromTier5 = await accountRepository.find({
|
||||
where: {
|
||||
tier: TIERS.TIER5,
|
||||
},
|
||||
relations: {
|
||||
user: true,
|
||||
userArchive: true
|
||||
},
|
||||
});
|
||||
|
||||
// 第五階層が保持する有効なライセンスを取得
|
||||
const licenseRepository = datasource.getRepository(License);
|
||||
// 現在時刻を起点とした23:59:59の日付
|
||||
const currentDateWithDayEndTime = new DateWithDayEndTime();
|
||||
const avairableLicenses = await licenseRepository
|
||||
.createQueryBuilder("license")
|
||||
.innerJoin("license.account", "account")
|
||||
.where("account.tier = :tier", { tier: TIERS.TIER5 })
|
||||
.andWhere(
|
||||
"(license.expiry_date > :currentDateWithDayEndTime OR license.expiry_date IS NULL)",
|
||||
{
|
||||
currentDateWithDayEndTime,
|
||||
}
|
||||
)
|
||||
.andWhere("license.status IN (:...statuses)", {
|
||||
statuses: [
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE,
|
||||
],
|
||||
})
|
||||
.getMany();
|
||||
|
||||
// 第五階層が保持するその月に発行したライセンスを取得
|
||||
|
||||
// timestamp型のDB列をyyyymm形式に変換する場合RDBMS依存の処理が必要になるので、最初の日~最後の日のbetweenで判断する
|
||||
const year = parseInt(targetMonthYYYYMM.slice(0, 4), 10);
|
||||
const month = parseInt(targetMonthYYYYMM.slice(4), 10) - 1; // JavaScriptの月は0-indexed
|
||||
const targetMonthStartDate = new Date(year, month, 1, 0, 0, 0, 0);
|
||||
const targetMonthEndDate = new Date(year, month + 1, 0, 23, 59, 59, 0); // mysql上ミリ秒999を指定すると四捨五入されて翌日になってしまうのでミリ秒は0を指定
|
||||
|
||||
const licensesIssuedInTargetMonth = await licenseRepository
|
||||
.createQueryBuilder("license")
|
||||
.innerJoin("license.account", "account")
|
||||
.where("account.tier = :tier", { tier: TIERS.TIER5 })
|
||||
.andWhere({
|
||||
created_at: Between(targetMonthStartDate, targetMonthEndDate),
|
||||
})
|
||||
.andWhere("license.status IN (:...statuses)", {
|
||||
statuses: [
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE,
|
||||
],
|
||||
})
|
||||
.getMany();
|
||||
|
||||
// 第五階層が保持するその月に失効したライセンスを取得
|
||||
const licensesExpiredInTargetMonth = await licenseRepository
|
||||
.createQueryBuilder("license")
|
||||
.innerJoin("license.account", "account")
|
||||
.where("account.tier = :tier", { tier: TIERS.TIER5 })
|
||||
.andWhere({
|
||||
expiry_date: Between(targetMonthStartDate, targetMonthEndDate),
|
||||
})
|
||||
.andWhere("license.status IN (:...statuses)", {
|
||||
statuses: [
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE,
|
||||
],
|
||||
})
|
||||
.getMany();
|
||||
|
||||
// 第五階層がその月におこなったライセンス切り替え情報を取得
|
||||
const licenseAllocationHistory = datasource.getRepository(
|
||||
LicenseAllocationHistory
|
||||
);
|
||||
const switchedlicensesInTargetMonth = await licenseAllocationHistory
|
||||
.createQueryBuilder("licenseAllocationHistory")
|
||||
.innerJoinAndSelect("licenseAllocationHistory.user", "user")
|
||||
.innerJoin("user.account", "account")
|
||||
.where("account.tier = :tier", { tier: TIERS.TIER5 })
|
||||
.andWhere("licenseAllocationHistory.switch_from_type IN (:...types)", {
|
||||
types: [SWITCH_FROM_TYPE.CARD, SWITCH_FROM_TYPE.TRIAL],
|
||||
})
|
||||
.andWhere({
|
||||
executed_at: Between(targetMonthStartDate, targetMonthEndDate),
|
||||
})
|
||||
.getMany();
|
||||
|
||||
return {
|
||||
accountsAndUsersFromTier5,
|
||||
avairableLicenses,
|
||||
licensesIssuedInTargetMonth,
|
||||
licensesExpiredInTargetMonth,
|
||||
switchedlicensesInTargetMonth,
|
||||
};
|
||||
} catch (e) {
|
||||
context.log("getBaseData failed.");
|
||||
context.error(e);
|
||||
throw e;
|
||||
} finally {
|
||||
context.log("[OUT]getBaseData");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 集計元のデータ(削除されたアカウントの情報)をDBから取得する処理
|
||||
* @param context
|
||||
* @param targetMonthYYYYMM
|
||||
* @param datasource
|
||||
* 内部関数だがテスト用にexportする
|
||||
*/
|
||||
export async function getBaseDataFromDeletedAccounts(
|
||||
context: InvocationContext,
|
||||
targetMonthYYYYMM: string,
|
||||
datasource: DataSource
|
||||
): Promise<BaseDataFromDeletedAccounts> {
|
||||
try {
|
||||
context.log("[IN]getBaseDataFromDeletedAccounts");
|
||||
|
||||
// 第五階層のアカウントとユーザーを取得する
|
||||
// 第五のアカウントを取得
|
||||
const accountArchiveRepository = datasource.getRepository(AccountArchive);
|
||||
const deletedAccountsAndUsersFromTier5 = await accountArchiveRepository.find({
|
||||
where: {
|
||||
tier: TIERS.TIER5,
|
||||
},
|
||||
relations: { userArchive: true},
|
||||
});
|
||||
|
||||
// 第五階層が保持する有効なライセンスを取得
|
||||
const licenseArchiveRepository = datasource.getRepository(LicenseArchive);
|
||||
// 現在時刻を起点とした23:59:59の日付
|
||||
const currentDateWithDayEndTime = new DateWithDayEndTime();
|
||||
const deletedAvairableLicenses = await licenseArchiveRepository
|
||||
.createQueryBuilder("license_archive")
|
||||
.innerJoin("license_archive.accountArchive", "accountArchive")
|
||||
.where("accountArchive.tier = :tier", { tier: TIERS.TIER5 })
|
||||
.andWhere(
|
||||
"(license_archive.expiry_date > :currentDateWithDayEndTime OR license_archive.expiry_date IS NULL)",
|
||||
{
|
||||
currentDateWithDayEndTime,
|
||||
}
|
||||
)
|
||||
.andWhere("license_archive.status IN (:...statuses)", {
|
||||
statuses: [
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE,
|
||||
],
|
||||
})
|
||||
.getMany();
|
||||
|
||||
// 第五階層が保持するその月に発行したライセンスを取得
|
||||
|
||||
// timestamp型のDB列をyyyymm形式に変換する場合RDBMS依存の処理が必要になるので、最初の日~最後の日のbetweenで判断する
|
||||
const year = parseInt(targetMonthYYYYMM.slice(0, 4), 10);
|
||||
const month = parseInt(targetMonthYYYYMM.slice(4), 10) - 1; // JavaScriptの月は0-indexed
|
||||
const targetMonthStartDate = new Date(year, month, 1, 0, 0, 0, 0);
|
||||
const targetMonthEndDate = new Date(year, month + 1, 0, 23, 59, 59, 0); // mysql上ミリ秒999を指定すると四捨五入されて翌日になってしまうのでミリ秒は0を指定
|
||||
|
||||
const deletedLicensesIssuedInTargetMonth = await licenseArchiveRepository
|
||||
.createQueryBuilder("license_archive")
|
||||
.innerJoin("license_archive.accountArchive", "accountArchive")
|
||||
.where("accountArchive.tier = :tier", { tier: TIERS.TIER5 })
|
||||
.andWhere({
|
||||
created_at: Between(targetMonthStartDate, targetMonthEndDate),
|
||||
})
|
||||
.andWhere("license_archive.status IN (:...statuses)", {
|
||||
statuses: [
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE,
|
||||
],
|
||||
})
|
||||
.getMany();
|
||||
|
||||
// 第五階層が保持するその月に失効したライセンスを取得
|
||||
const deletedLicensesExpiredInTargetMonth = await licenseArchiveRepository
|
||||
.createQueryBuilder("license_archive")
|
||||
.innerJoin("license_archive.accountArchive", "accountArchive")
|
||||
.where("accountArchive.tier = :tier", { tier: TIERS.TIER5 })
|
||||
.andWhere({
|
||||
expiry_date: Between(targetMonthStartDate, targetMonthEndDate),
|
||||
})
|
||||
.andWhere("license_archive.status IN (:...statuses)", {
|
||||
statuses: [
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE,
|
||||
],
|
||||
})
|
||||
.getMany();
|
||||
|
||||
// 第五階層がその月におこなったライセンス切り替え情報を取得
|
||||
const licenseAllocationHistoryArchive = datasource.getRepository(
|
||||
LicenseAllocationHistoryArchive
|
||||
);
|
||||
const deletedSwitchedlicensesInTargetMonth =
|
||||
await licenseAllocationHistoryArchive
|
||||
.createQueryBuilder("licenseAllocationHistory_archive")
|
||||
.innerJoinAndSelect(
|
||||
"licenseAllocationHistory_archive.userArchive",
|
||||
"userArchive"
|
||||
)
|
||||
.innerJoin("userArchive.accountArchive", "accountArchive")
|
||||
.where("accountArchive.tier = :tier", { tier: TIERS.TIER5 })
|
||||
.andWhere(
|
||||
"licenseAllocationHistory_archive.switch_from_type IN (:...types)",
|
||||
{
|
||||
types: [SWITCH_FROM_TYPE.CARD, SWITCH_FROM_TYPE.TRIAL],
|
||||
}
|
||||
)
|
||||
.andWhere({
|
||||
executed_at: Between(targetMonthStartDate, targetMonthEndDate),
|
||||
})
|
||||
.getMany();
|
||||
|
||||
return {
|
||||
deletedAccountsAndUsersFromTier5,
|
||||
deletedAvairableLicenses,
|
||||
deletedLicensesIssuedInTargetMonth,
|
||||
deletedLicensesExpiredInTargetMonth,
|
||||
deletedSwitchedlicensesInTargetMonth,
|
||||
};
|
||||
} catch (e) {
|
||||
context.log("getBaseDataFromDeletedAccounts failed.");
|
||||
context.error(e);
|
||||
throw e;
|
||||
} finally {
|
||||
context.log("[OUT]getBaseDataFromDeletedAccounts");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ライセンス数分析処理:Azure Functionの関数として呼び出される処理
|
||||
* @param myTimer
|
||||
* @param context
|
||||
*/
|
||||
export async function analysisLicenses(
|
||||
myTimer: Timer,
|
||||
context: InvocationContext
|
||||
): Promise<void> {
|
||||
context.log("[IN]analysisLicenses");
|
||||
|
||||
dotenv.config({ path: ".env" });
|
||||
dotenv.config({ path: ".env.local", override: true });
|
||||
let datasource: DataSource;
|
||||
try {
|
||||
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_CCB,
|
||||
entities: [
|
||||
User,
|
||||
Account,
|
||||
License,
|
||||
LicenseAllocationHistory,
|
||||
UserArchive,
|
||||
AccountArchive,
|
||||
LicenseArchive,
|
||||
LicenseAllocationHistoryArchive,
|
||||
],
|
||||
});
|
||||
await datasource.initialize();
|
||||
} catch (e) {
|
||||
context.log("database initialize failed.");
|
||||
context.error(e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
const blobstorageService = new BlobstorageService();
|
||||
|
||||
try {
|
||||
// 現在の日付より、先月の年月をYYYYMM形式で取得
|
||||
const currentDate = new Date();
|
||||
currentDate.setMonth(currentDate.getMonth() - 1);
|
||||
const year = currentDate.getFullYear();
|
||||
const month = (currentDate.getMonth() + 1).toString().padStart(2, "0"); // 月は0から始まるため+1する
|
||||
const formattedDate = `${year}${month}`;
|
||||
|
||||
await analysisLicensesProcessing(
|
||||
context,
|
||||
formattedDate,
|
||||
datasource,
|
||||
blobstorageService
|
||||
);
|
||||
} catch (e) {
|
||||
context.log("analysisLicensesProcessing failed.");
|
||||
context.error(e);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
context.log("[OUT]analysisLicenses");
|
||||
}
|
||||
}
|
||||
|
||||
app.timer("analysisLicenses", {
|
||||
schedule: "0 0 0 1 * *",
|
||||
handler: analysisLicenses,
|
||||
});
|
||||
|
||||
|
||||
type BaseData = {
|
||||
// 存在するアカウントの集計元情報
|
||||
accountsAndUsersFromTier5: Account[];
|
||||
avairableLicenses: License[];
|
||||
licensesIssuedInTargetMonth: License[];
|
||||
licensesExpiredInTargetMonth: License[];
|
||||
switchedlicensesInTargetMonth: LicenseAllocationHistory[];
|
||||
};
|
||||
|
||||
type BaseDataFromDeletedAccounts = {
|
||||
// 削除されたアカウントの集計元情報
|
||||
deletedAccountsAndUsersFromTier5: AccountArchive[];
|
||||
deletedAvairableLicenses: LicenseArchive[];
|
||||
deletedLicensesIssuedInTargetMonth: LicenseArchive[];
|
||||
deletedLicensesExpiredInTargetMonth: LicenseArchive[];
|
||||
deletedSwitchedlicensesInTargetMonth: LicenseAllocationHistoryArchive[];
|
||||
};
|
||||
737
dictation_function/src/test/analysisLicenses.spec.ts
Normal file
737
dictation_function/src/test/analysisLicenses.spec.ts
Normal file
@ -0,0 +1,737 @@
|
||||
import { DataSource } from "typeorm";
|
||||
import {
|
||||
getBaseData,
|
||||
getBaseDataFromDeletedAccounts,
|
||||
} from "../functions/analysisLicenses";
|
||||
import {
|
||||
makeTestAccount,
|
||||
makeTestUserArchive,
|
||||
createLicense,
|
||||
createLicenseAllocationHistory,
|
||||
makeTestAccountArchive,
|
||||
createLicenseArchive,
|
||||
createLicenseAllocationHistoryArchive,
|
||||
} from "./common/utility";
|
||||
import * as dotenv from "dotenv";
|
||||
import {
|
||||
DateWithZeroTime,
|
||||
ExpirationThresholdDate,
|
||||
} from "../common/types/types";
|
||||
import { InvocationContext } from "@azure/functions";
|
||||
import {
|
||||
LICENSE_ALLOCATED_STATUS,
|
||||
SWITCH_FROM_TYPE,
|
||||
} from "../constants";
|
||||
describe("analysisLicenses", () => {
|
||||
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("getBaseData取得情報の確認", async () => {
|
||||
if (!source) fail();
|
||||
const context = new InvocationContext();
|
||||
|
||||
const currentDate = new DateWithZeroTime();
|
||||
const expiringSoonDate = new ExpirationThresholdDate(currentDate.getTime());
|
||||
|
||||
// 現在の日付を取得
|
||||
const nowDate = new Date();
|
||||
|
||||
// 先月の日付を取得
|
||||
const lastMonth = new Date(nowDate);
|
||||
lastMonth.setMonth(nowDate.getMonth() - 1);
|
||||
const lastMonthYYYYMM = `${lastMonth.getFullYear()}${(
|
||||
lastMonth.getMonth() + 1
|
||||
)
|
||||
.toString()
|
||||
.padStart(2, "0")}`;
|
||||
|
||||
// 先々月の日付を取得
|
||||
const last2Month = new Date(nowDate);
|
||||
last2Month.setMonth(nowDate.getMonth() - 2);
|
||||
|
||||
// tier4とtier5のアカウント+管理者を作る(tier4は対象外確認用)
|
||||
// 第五アカウント:2件
|
||||
// 第五ユーザー:2件
|
||||
const { account: account4, admin: admin4 } = await makeTestAccount(
|
||||
source,
|
||||
{ tier: 4 },
|
||||
{ external_id: "external_id_tier4admin" }
|
||||
);
|
||||
const { account: account5_1, admin: admin5_1 } = await makeTestAccount(
|
||||
source,
|
||||
{
|
||||
tier: 5,
|
||||
parent_account_id: account4.id,
|
||||
},
|
||||
{ external_id: "external_id_tier5admin1" }
|
||||
);
|
||||
const { account: account5_2, admin: admin5_2 } = await makeTestAccount(
|
||||
source,
|
||||
{
|
||||
tier: 5,
|
||||
parent_account_id: account4.id,
|
||||
},
|
||||
{ external_id: "external_id_tier5admin2" }
|
||||
);
|
||||
|
||||
// 削除ユーザを作成する
|
||||
const userArchive5 = await makeTestUserArchive(source, {
|
||||
account_id: account5_1.id,
|
||||
});
|
||||
// 第五階層以外だとヒットしないことの確認
|
||||
const userArchive4 = await makeTestUserArchive(source, {
|
||||
account_id: account4.id,
|
||||
});
|
||||
|
||||
// 所有ライセンス
|
||||
// 条件:
|
||||
// ・第五アカウント
|
||||
// ・ステータス:ALLOCATED/REUSABLE/UNALLOCATED
|
||||
// ・作成日:2か月前
|
||||
// ・期限:null or 14日後
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
null,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
last2Month
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
2,
|
||||
expiringSoonDate,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
last2Month
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
3,
|
||||
expiringSoonDate,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
last2Month
|
||||
);
|
||||
// deleteはヒットしないことの確認
|
||||
await createLicense(
|
||||
source,
|
||||
4,
|
||||
expiringSoonDate,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.DELETED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
last2Month
|
||||
);
|
||||
|
||||
// その月に発行したライセンスを作成
|
||||
// 条件:
|
||||
// ・第五アカウント
|
||||
// ・ステータス:ALLOCATED/REUSABLE/UNALLOCATED
|
||||
// ・作成日:今日から1か月前
|
||||
// ・期限:14日後
|
||||
// ※条件的に「所有ライセンス」にもカウントされる(+3)
|
||||
await createLicense(
|
||||
source,
|
||||
11,
|
||||
expiringSoonDate,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
lastMonth
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
12,
|
||||
expiringSoonDate,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
lastMonth
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
13,
|
||||
expiringSoonDate,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
lastMonth
|
||||
);
|
||||
// deleteはヒットしないことの確認
|
||||
await createLicense(
|
||||
source,
|
||||
14,
|
||||
expiringSoonDate,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.DELETED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
lastMonth
|
||||
);
|
||||
|
||||
// その月に失効したライセンスを作成
|
||||
// 条件:
|
||||
// ・第五アカウント
|
||||
// ・ステータス:ALLOCATED/REUSABLE/UNALLOCATED
|
||||
// ・作成日:今日から2か月前
|
||||
// ・期限:先月
|
||||
await createLicense(
|
||||
source,
|
||||
21,
|
||||
lastMonth,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
last2Month
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
22,
|
||||
lastMonth,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
last2Month
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
23,
|
||||
lastMonth,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
last2Month
|
||||
);
|
||||
// deleteはヒットしないことの確認
|
||||
await createLicense(
|
||||
source,
|
||||
24,
|
||||
lastMonth,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.DELETED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
last2Month
|
||||
);
|
||||
// 先々月はヒットしないことの確認
|
||||
await createLicense(
|
||||
source,
|
||||
25,
|
||||
last2Month,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
last2Month
|
||||
);
|
||||
|
||||
// 第五階層がその月におこなったライセンス切り替え情報を作成
|
||||
// 条件:
|
||||
// ・第五アカウント
|
||||
// ・実行日時:先月
|
||||
// ・切り替えタイプ:CARD/TRIAL
|
||||
await createLicenseAllocationHistory(
|
||||
source,
|
||||
1,
|
||||
admin5_1.id,
|
||||
1,
|
||||
true,
|
||||
account5_1.id,
|
||||
lastMonth,
|
||||
SWITCH_FROM_TYPE.CARD
|
||||
);
|
||||
await createLicenseAllocationHistory(
|
||||
source,
|
||||
2,
|
||||
admin5_1.id,
|
||||
1,
|
||||
true,
|
||||
account5_1.id,
|
||||
lastMonth,
|
||||
SWITCH_FROM_TYPE.TRIAL
|
||||
);
|
||||
// SWITCH_FROM_TYPE.NONEではヒットしないことの確認
|
||||
await createLicenseAllocationHistory(
|
||||
source,
|
||||
3,
|
||||
admin5_1.id,
|
||||
1,
|
||||
true,
|
||||
account5_1.id,
|
||||
lastMonth,
|
||||
SWITCH_FROM_TYPE.NONE
|
||||
);
|
||||
// 先々月の登録ではヒットしないことの確認
|
||||
await createLicenseAllocationHistory(
|
||||
source,
|
||||
4,
|
||||
admin5_1.id,
|
||||
1,
|
||||
true,
|
||||
account5_1.id,
|
||||
last2Month,
|
||||
SWITCH_FROM_TYPE.TRIAL
|
||||
);
|
||||
|
||||
const result = await getBaseData(context, lastMonthYYYYMM, source);
|
||||
expect(result.accountsAndUsersFromTier5).toHaveLength(2);
|
||||
expect(result.accountsAndUsersFromTier5[0].id).toBe(account5_1.id);
|
||||
expect(result.accountsAndUsersFromTier5[1].id).toBe(account5_2.id);
|
||||
|
||||
expect(result.accountsAndUsersFromTier5[0].user).toHaveLength(1);
|
||||
expect(result.accountsAndUsersFromTier5[1].user).toHaveLength(1);
|
||||
if (
|
||||
result.accountsAndUsersFromTier5[0].user &&
|
||||
result.accountsAndUsersFromTier5[1].user
|
||||
) {
|
||||
expect(result.accountsAndUsersFromTier5[0].user[0].id).toBe(admin5_1.id);
|
||||
expect(result.accountsAndUsersFromTier5[1].user[0].id).toBe(admin5_2.id);
|
||||
} else {
|
||||
throw new Error("ユーザー取得できていないので失敗");
|
||||
}
|
||||
|
||||
expect(result.accountsAndUsersFromTier5[0].userArchive).toHaveLength(1);
|
||||
if (result.accountsAndUsersFromTier5[0].userArchive) {
|
||||
expect(result.accountsAndUsersFromTier5[0].userArchive[0].id).toBe(
|
||||
userArchive5.id
|
||||
);
|
||||
} else {
|
||||
throw new Error("ユーザー取得できていないので失敗");
|
||||
}
|
||||
|
||||
expect(result.avairableLicenses).toHaveLength(6);
|
||||
expect(result.avairableLicenses[0].id).toBe(1);
|
||||
expect(result.avairableLicenses[1].id).toBe(2);
|
||||
expect(result.avairableLicenses[2].id).toBe(3);
|
||||
expect(result.avairableLicenses[3].id).toBe(11);
|
||||
expect(result.avairableLicenses[4].id).toBe(12);
|
||||
expect(result.avairableLicenses[5].id).toBe(13);
|
||||
|
||||
expect(result.licensesIssuedInTargetMonth).toHaveLength(3);
|
||||
expect(result.licensesIssuedInTargetMonth[0].id).toBe(11);
|
||||
expect(result.licensesIssuedInTargetMonth[1].id).toBe(12);
|
||||
expect(result.licensesIssuedInTargetMonth[2].id).toBe(13);
|
||||
|
||||
expect(result.licensesExpiredInTargetMonth).toHaveLength(3);
|
||||
expect(result.licensesExpiredInTargetMonth[0].id).toBe(21);
|
||||
expect(result.licensesExpiredInTargetMonth[1].id).toBe(22);
|
||||
expect(result.licensesExpiredInTargetMonth[2].id).toBe(23);
|
||||
|
||||
expect(result.switchedlicensesInTargetMonth).toHaveLength(2);
|
||||
expect(result.switchedlicensesInTargetMonth[0].id).toBe(1);
|
||||
expect(result.switchedlicensesInTargetMonth[1].id).toBe(2);
|
||||
|
||||
});
|
||||
|
||||
|
||||
it("getBaseDataFromDeletedAccounts取得情報の確認", async () => {
|
||||
if (!source) fail();
|
||||
const context = new InvocationContext();
|
||||
|
||||
const currentDate = new DateWithZeroTime();
|
||||
const expiringSoonDate = new ExpirationThresholdDate(currentDate.getTime());
|
||||
|
||||
// 現在の日付を取得
|
||||
const nowDate = new Date();
|
||||
|
||||
// 先月の日付を取得
|
||||
const lastMonth = new Date(nowDate);
|
||||
lastMonth.setMonth(nowDate.getMonth() - 1);
|
||||
const lastMonthYYYYMM = `${lastMonth.getFullYear()}${(
|
||||
lastMonth.getMonth() + 1
|
||||
)
|
||||
.toString()
|
||||
.padStart(2, "0")}`;
|
||||
|
||||
// 先々月の日付を取得
|
||||
const last2Month = new Date(nowDate);
|
||||
last2Month.setMonth(nowDate.getMonth() - 2);
|
||||
|
||||
// tier4とtier5のアカウント+管理者を作る(tier4は対象外確認用)
|
||||
// 第五アカウント:2件
|
||||
// 第五ユーザー:2件
|
||||
const { account: account4, admin: admin4 } = await makeTestAccountArchive(
|
||||
source,
|
||||
{ tier: 4 },
|
||||
{ external_id: "external_id_tier4admin" }
|
||||
);
|
||||
const { account: account5_1, admin: admin5_1 } =
|
||||
await makeTestAccountArchive(
|
||||
source,
|
||||
{
|
||||
tier: 5,
|
||||
parent_account_id: account4.id,
|
||||
},
|
||||
{ external_id: "external_id_tier5admin1" }
|
||||
);
|
||||
const { account: account5_2, admin: admin5_2 } =
|
||||
await makeTestAccountArchive(
|
||||
source,
|
||||
{
|
||||
tier: 5,
|
||||
parent_account_id: account4.id,
|
||||
},
|
||||
{ external_id: "external_id_tier5admin2" }
|
||||
);
|
||||
|
||||
// 所有ライセンス
|
||||
// 条件:
|
||||
// ・第五アカウント
|
||||
// ・ステータス:ALLOCATED/REUSABLE/UNALLOCATED
|
||||
// ・作成日:2か月前
|
||||
// ・期限:null or 14日後
|
||||
await createLicenseArchive(
|
||||
source,
|
||||
1,
|
||||
null,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
last2Month
|
||||
);
|
||||
await createLicenseArchive(
|
||||
source,
|
||||
2,
|
||||
expiringSoonDate,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
last2Month
|
||||
);
|
||||
await createLicenseArchive(
|
||||
source,
|
||||
3,
|
||||
expiringSoonDate,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
last2Month
|
||||
);
|
||||
// deleteはヒットしないことの確認
|
||||
await createLicenseArchive(
|
||||
source,
|
||||
4,
|
||||
expiringSoonDate,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.DELETED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
last2Month
|
||||
);
|
||||
|
||||
// その月に発行したライセンスを作成
|
||||
// 条件:
|
||||
// ・第五アカウント
|
||||
// ・ステータス:ALLOCATED/REUSABLE/UNALLOCATED
|
||||
// ・作成日:今日から1か月前
|
||||
// ・期限:14日後
|
||||
// ※条件的に「所有ライセンス」にもカウントされる(+3)
|
||||
await createLicenseArchive(
|
||||
source,
|
||||
11,
|
||||
expiringSoonDate,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
lastMonth
|
||||
);
|
||||
await createLicenseArchive(
|
||||
source,
|
||||
12,
|
||||
expiringSoonDate,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
lastMonth
|
||||
);
|
||||
await createLicenseArchive(
|
||||
source,
|
||||
13,
|
||||
expiringSoonDate,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
lastMonth
|
||||
);
|
||||
// deleteはヒットしないことの確認
|
||||
await createLicenseArchive(
|
||||
source,
|
||||
14,
|
||||
expiringSoonDate,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.DELETED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
lastMonth
|
||||
);
|
||||
|
||||
// その月に失効したライセンスを作成
|
||||
// 条件:
|
||||
// ・第五アカウント
|
||||
// ・ステータス:ALLOCATED/REUSABLE/UNALLOCATED
|
||||
// ・作成日:今日から2か月前
|
||||
// ・期限:先月
|
||||
await createLicenseArchive(
|
||||
source,
|
||||
21,
|
||||
lastMonth,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
last2Month
|
||||
);
|
||||
await createLicenseArchive(
|
||||
source,
|
||||
22,
|
||||
lastMonth,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
last2Month
|
||||
);
|
||||
await createLicenseArchive(
|
||||
source,
|
||||
23,
|
||||
lastMonth,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
last2Month
|
||||
);
|
||||
// deleteはヒットしないことの確認
|
||||
await createLicenseArchive(
|
||||
source,
|
||||
24,
|
||||
lastMonth,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.DELETED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
last2Month
|
||||
);
|
||||
// 先々月はヒットしないことの確認
|
||||
await createLicenseArchive(
|
||||
source,
|
||||
25,
|
||||
last2Month,
|
||||
account5_1.id,
|
||||
"STANDARD",
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
last2Month
|
||||
);
|
||||
|
||||
// 第五階層がその月におこなったライセンス切り替え情報を作成
|
||||
// 条件:
|
||||
// ・第五アカウント
|
||||
// ・実行日時:先月
|
||||
// ・切り替えタイプ:CARD/TRIAL
|
||||
await createLicenseAllocationHistoryArchive(
|
||||
source,
|
||||
1,
|
||||
admin5_1.id,
|
||||
1,
|
||||
true,
|
||||
account5_1.id,
|
||||
lastMonth,
|
||||
SWITCH_FROM_TYPE.CARD
|
||||
);
|
||||
await createLicenseAllocationHistoryArchive(
|
||||
source,
|
||||
2,
|
||||
admin5_1.id,
|
||||
1,
|
||||
true,
|
||||
account5_1.id,
|
||||
lastMonth,
|
||||
SWITCH_FROM_TYPE.TRIAL
|
||||
);
|
||||
// SWITCH_FROM_TYPE.NONEではヒットしないことの確認
|
||||
await createLicenseAllocationHistoryArchive(
|
||||
source,
|
||||
3,
|
||||
admin5_1.id,
|
||||
1,
|
||||
true,
|
||||
account5_1.id,
|
||||
lastMonth,
|
||||
SWITCH_FROM_TYPE.NONE
|
||||
);
|
||||
// 先々月の登録ではヒットしないことの確認
|
||||
await createLicenseAllocationHistoryArchive(
|
||||
source,
|
||||
4,
|
||||
admin5_1.id,
|
||||
1,
|
||||
true,
|
||||
account5_1.id,
|
||||
last2Month,
|
||||
SWITCH_FROM_TYPE.TRIAL
|
||||
);
|
||||
|
||||
const result = await getBaseDataFromDeletedAccounts(context, lastMonthYYYYMM, source);
|
||||
expect(result.deletedAccountsAndUsersFromTier5).toHaveLength(2);
|
||||
expect(result.deletedAccountsAndUsersFromTier5[0].id).toBe(account5_1.id);
|
||||
expect(result.deletedAccountsAndUsersFromTier5[1].id).toBe(account5_2.id);
|
||||
|
||||
expect(result.deletedAccountsAndUsersFromTier5[0].userArchive).toHaveLength(
|
||||
1
|
||||
);
|
||||
expect(result.deletedAccountsAndUsersFromTier5[1].userArchive).toHaveLength(
|
||||
1
|
||||
);
|
||||
if (
|
||||
result.deletedAccountsAndUsersFromTier5[0].userArchive &&
|
||||
result.deletedAccountsAndUsersFromTier5[1].userArchive
|
||||
) {
|
||||
expect(result.deletedAccountsAndUsersFromTier5[0].userArchive[0].id).toBe(
|
||||
admin5_1.id
|
||||
);
|
||||
expect(result.deletedAccountsAndUsersFromTier5[1].userArchive[0].id).toBe(
|
||||
admin5_2.id
|
||||
);
|
||||
}
|
||||
|
||||
expect(result.deletedAvairableLicenses).toHaveLength(6);
|
||||
expect(result.deletedAvairableLicenses[0].id).toBe(1);
|
||||
expect(result.deletedAvairableLicenses[1].id).toBe(2);
|
||||
expect(result.deletedAvairableLicenses[2].id).toBe(3);
|
||||
expect(result.deletedAvairableLicenses[3].id).toBe(11);
|
||||
expect(result.deletedAvairableLicenses[4].id).toBe(12);
|
||||
expect(result.deletedAvairableLicenses[5].id).toBe(13);
|
||||
|
||||
expect(result.deletedLicensesIssuedInTargetMonth).toHaveLength(3);
|
||||
expect(result.deletedLicensesIssuedInTargetMonth[0].id).toBe(11);
|
||||
expect(result.deletedLicensesIssuedInTargetMonth[1].id).toBe(12);
|
||||
expect(result.deletedLicensesIssuedInTargetMonth[2].id).toBe(13);
|
||||
|
||||
expect(result.deletedLicensesExpiredInTargetMonth).toHaveLength(3);
|
||||
expect(result.deletedLicensesExpiredInTargetMonth[0].id).toBe(21);
|
||||
expect(result.deletedLicensesExpiredInTargetMonth[1].id).toBe(22);
|
||||
expect(result.deletedLicensesExpiredInTargetMonth[2].id).toBe(23);
|
||||
|
||||
expect(result.deletedSwitchedlicensesInTargetMonth).toHaveLength(2);
|
||||
expect(result.deletedSwitchedlicensesInTargetMonth[0].id).toBe(1);
|
||||
expect(result.deletedSwitchedlicensesInTargetMonth[1].id).toBe(2);
|
||||
});
|
||||
|
||||
});
|
||||
@ -1,9 +1,14 @@
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { DataSource } from "typeorm";
|
||||
import { User } from "../../entity/user.entity";
|
||||
import { Account } from "../../entity/account.entity";
|
||||
import { User, UserArchive } from "../../entity/user.entity";
|
||||
import { Account, AccountArchive } from "../../entity/account.entity";
|
||||
import { ADMIN_ROLES, USER_ROLES } from "../../constants";
|
||||
import { License, LicenseAllocationHistory } from "../../entity/license.entity";
|
||||
import {
|
||||
License,
|
||||
LicenseAllocationHistory,
|
||||
LicenseArchive,
|
||||
LicenseAllocationHistoryArchive
|
||||
} from "../../entity/license.entity";
|
||||
|
||||
type InitialTestDBState = {
|
||||
tier1Accounts: { account: Account; users: User[] }[];
|
||||
@ -25,8 +30,25 @@ type OverrideUser = Omit<
|
||||
"id" | "account" | "license" | "userGroupMembers"
|
||||
>;
|
||||
|
||||
type OverrideAccountArchive = Omit<
|
||||
AccountArchive,
|
||||
"id" | "primary_admin_user_id" | "secondary_admin_user_id" | "user"
|
||||
>;
|
||||
|
||||
// 上書きされたら困る項目を除外したUser型
|
||||
type OverrideUserArchive = Omit<
|
||||
UserArchive,
|
||||
"id" | "account" | "license" | "userGroupMembers"
|
||||
>;
|
||||
|
||||
type AccountDefault = { [K in keyof OverrideAccount]?: OverrideAccount[K] };
|
||||
type UserDefault = { [K in keyof OverrideUser]?: OverrideUser[K] };
|
||||
type AccountArchiveDefault = {
|
||||
[K in keyof OverrideAccountArchive]?: OverrideAccountArchive[K];
|
||||
};
|
||||
type UserArchiveDefault = {
|
||||
[K in keyof OverrideUserArchive]?: OverrideUserArchive[K];
|
||||
};
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: 指定したプロパティを上書きしたユーザーを作成する
|
||||
@ -50,7 +72,6 @@ export const makeTestUser = async (
|
||||
auto_renew: d?.auto_renew ?? true,
|
||||
notification: d?.notification ?? true,
|
||||
encryption: d?.encryption ?? true,
|
||||
encryption_password: d?.encryption_password,
|
||||
prompt: d?.prompt ?? true,
|
||||
created_by: d?.created_by ?? "test_runner",
|
||||
created_at: d?.created_at ?? new Date(),
|
||||
@ -118,7 +139,6 @@ export const makeTestAccount = async (
|
||||
auto_renew: d?.auto_renew ?? true,
|
||||
notification: d?.notification ?? true,
|
||||
encryption: d?.encryption ?? true,
|
||||
encryption_password: d?.encryption_password ?? "password",
|
||||
prompt: d?.prompt ?? true,
|
||||
deleted_at: d?.deleted_at ?? "",
|
||||
created_by: d?.created_by ?? "test_runner",
|
||||
@ -175,7 +195,8 @@ export const createLicense = async (
|
||||
allocated_user_id: number | null,
|
||||
order_id: number | null,
|
||||
deleted_at: Date | null,
|
||||
delete_order_id: number | null
|
||||
delete_order_id: number | null,
|
||||
created_at?: Date
|
||||
): Promise<void> => {
|
||||
const { identifiers } = await datasource.getRepository(License).insert({
|
||||
id: licenseId,
|
||||
@ -188,13 +209,41 @@ export const createLicense = async (
|
||||
deleted_at: deleted_at,
|
||||
delete_order_id: delete_order_id,
|
||||
created_by: "test_runner",
|
||||
created_at: new Date(),
|
||||
created_at: created_at ? created_at : new Date(),
|
||||
updated_by: "updater",
|
||||
updated_at: new Date(),
|
||||
});
|
||||
identifiers.pop() as License;
|
||||
};
|
||||
|
||||
export const createLicenseAllocationHistory = async (
|
||||
datasource: DataSource,
|
||||
id: number,
|
||||
user_id: number,
|
||||
license_id: number,
|
||||
is_allocated: boolean,
|
||||
account_id: number,
|
||||
executed_at: Date,
|
||||
switch_from_type: string,
|
||||
): Promise<void> => {
|
||||
const { identifiers } = await datasource
|
||||
.getRepository(LicenseAllocationHistory)
|
||||
.insert({
|
||||
id: id,
|
||||
user_id: user_id,
|
||||
license_id: license_id,
|
||||
is_allocated: is_allocated,
|
||||
account_id: account_id,
|
||||
executed_at: executed_at,
|
||||
switch_from_type: switch_from_type,
|
||||
created_by: "test_runner",
|
||||
created_at: new Date(),
|
||||
updated_by: "updater",
|
||||
updated_at: new Date(),
|
||||
});
|
||||
identifiers.pop() as LicenseAllocationHistory;
|
||||
};
|
||||
|
||||
export const selectLicenseByAllocatedUser = async (
|
||||
datasource: DataSource,
|
||||
userId: number
|
||||
@ -225,3 +274,202 @@ export const selectLicenseAllocationHistory = async (
|
||||
});
|
||||
return { licenseAllocationHistory };
|
||||
};
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: 指定したプロパティを上書きしたアーカイブユーザーを作成する
|
||||
* @param dataSource データソース
|
||||
* @param defaultUserValue User型と同等かつoptionalなプロパティを持つ上書き箇所指定用オブジェクト
|
||||
* @returns 作成したユーザー
|
||||
*/
|
||||
export const makeTestUserArchive = async (
|
||||
datasource: DataSource,
|
||||
defaultUserValue?: UserArchiveDefault
|
||||
): Promise<UserArchive> => {
|
||||
const d = defaultUserValue;
|
||||
const { identifiers } = await datasource.getRepository(UserArchive).insert({
|
||||
account_id: d?.account_id ?? -1,
|
||||
external_id: d?.external_id ?? uuidv4(),
|
||||
role: d?.role ?? `${ADMIN_ROLES.STANDARD} ${USER_ROLES.NONE}`,
|
||||
author_id: d?.author_id,
|
||||
accepted_eula_version: d?.accepted_eula_version ?? "1.0",
|
||||
accepted_dpa_version: d?.accepted_dpa_version ?? "1.0",
|
||||
email_verified: d?.email_verified ?? true,
|
||||
auto_renew: d?.auto_renew ?? true,
|
||||
notification: d?.notification ?? true,
|
||||
encryption: d?.encryption ?? true,
|
||||
prompt: d?.prompt ?? true,
|
||||
created_by: d?.created_by ?? "test_runner",
|
||||
created_at: d?.created_at ?? new Date(),
|
||||
updated_by: d?.updated_by ?? "updater",
|
||||
updated_at: d?.updated_at ?? new Date(),
|
||||
});
|
||||
const result = identifiers.pop() as User;
|
||||
|
||||
const userArchive = await datasource.getRepository(UserArchive).findOne({
|
||||
where: {
|
||||
id: result.id,
|
||||
},
|
||||
});
|
||||
if (!userArchive) {
|
||||
throw new Error("Unexpected null");
|
||||
}
|
||||
return userArchive;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: 指定したプロパティを上書きしたアカウントとその管理者ユーザーを作成する
|
||||
* @param dataSource データソース
|
||||
* @param defaultUserValue Account型と同等かつoptionalなプロパティを持つ上書き箇所指定用オブジェクト
|
||||
* @param defaultAdminUserValue User型と同等かつoptionalなプロパティを持つ上書き箇所指定用オブジェクト(account_id等の所属関係が破壊される上書きは無視する)
|
||||
* @returns 作成したアカウント
|
||||
*/
|
||||
export const makeTestAccountArchive = async (
|
||||
datasource: DataSource,
|
||||
defaultAccountValue?: AccountArchiveDefault,
|
||||
defaultAdminUserValue?: UserArchiveDefault,
|
||||
isPrimaryAdminNotExist?: boolean,
|
||||
isSecondaryAdminNotExist?: boolean
|
||||
): Promise<{ account: AccountArchive; admin: UserArchive }> => {
|
||||
let accountId: number;
|
||||
let userId: number;
|
||||
{
|
||||
const d = defaultAccountValue;
|
||||
const { identifiers } = await datasource
|
||||
.getRepository(AccountArchive)
|
||||
.insert({
|
||||
tier: d?.tier ?? 1,
|
||||
parent_account_id: d?.parent_account_id ?? undefined,
|
||||
country: d?.country ?? "US",
|
||||
delegation_permission: d?.delegation_permission ?? false,
|
||||
locked: d?.locked ?? false,
|
||||
verified: d?.verified ?? true,
|
||||
deleted_at: d?.deleted_at ?? "",
|
||||
created_by: d?.created_by ?? "test_runner",
|
||||
created_at: d?.created_at ?? new Date(),
|
||||
updated_by: d?.updated_by ?? "updater",
|
||||
updated_at: d?.updated_at ?? new Date(),
|
||||
});
|
||||
const result = identifiers.pop() as AccountArchive;
|
||||
accountId = result.id;
|
||||
}
|
||||
{
|
||||
const d = defaultAdminUserValue;
|
||||
const { identifiers } = await datasource.getRepository(UserArchive).insert({
|
||||
external_id: d?.external_id ?? uuidv4(),
|
||||
account_id: accountId,
|
||||
role: d?.role ?? "admin none",
|
||||
author_id: d?.author_id ?? undefined,
|
||||
accepted_eula_version: d?.accepted_eula_version ?? "1.0",
|
||||
accepted_dpa_version: d?.accepted_dpa_version ?? "1.0",
|
||||
email_verified: d?.email_verified ?? true,
|
||||
auto_renew: d?.auto_renew ?? true,
|
||||
notification: d?.notification ?? true,
|
||||
encryption: d?.encryption ?? true,
|
||||
prompt: d?.prompt ?? true,
|
||||
deleted_at: d?.deleted_at ?? "",
|
||||
created_by: d?.created_by ?? "test_runner",
|
||||
created_at: d?.created_at ?? new Date(),
|
||||
updated_by: d?.updated_by ?? "updater",
|
||||
updated_at: d?.updated_at ?? new Date(),
|
||||
});
|
||||
|
||||
const result = identifiers.pop() as UserArchive;
|
||||
userId = result.id;
|
||||
}
|
||||
|
||||
// Accountの管理者を設定する
|
||||
let secondaryAdminUserId: number | null = null;
|
||||
if (isPrimaryAdminNotExist && !isSecondaryAdminNotExist) {
|
||||
secondaryAdminUserId = userId;
|
||||
}
|
||||
await datasource.getRepository(AccountArchive).update(
|
||||
{ id: accountId },
|
||||
{
|
||||
primary_admin_user_id: isPrimaryAdminNotExist ? null : userId,
|
||||
secondary_admin_user_id: secondaryAdminUserId,
|
||||
}
|
||||
);
|
||||
|
||||
const account = await datasource.getRepository(AccountArchive).findOne({
|
||||
where: {
|
||||
id: accountId,
|
||||
},
|
||||
});
|
||||
|
||||
const admin = await datasource.getRepository(UserArchive).findOne({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
});
|
||||
if (!account || !admin) {
|
||||
throw new Error("Unexpected null");
|
||||
}
|
||||
|
||||
return {
|
||||
account: account,
|
||||
admin: admin,
|
||||
};
|
||||
};
|
||||
|
||||
export const createLicenseArchive = async (
|
||||
datasource: DataSource,
|
||||
licenseId: number,
|
||||
expiry_date: Date | null,
|
||||
accountId: number,
|
||||
type: string,
|
||||
status: string,
|
||||
allocated_user_id: number | null,
|
||||
order_id: number | null,
|
||||
deleted_at: Date | null,
|
||||
delete_order_id: number | null,
|
||||
created_at?: Date
|
||||
): Promise<void> => {
|
||||
const { identifiers } = await datasource
|
||||
.getRepository(LicenseArchive)
|
||||
.insert({
|
||||
id: licenseId,
|
||||
expiry_date: expiry_date,
|
||||
account_id: accountId,
|
||||
type: type,
|
||||
status: status,
|
||||
allocated_user_id: allocated_user_id,
|
||||
order_id: order_id,
|
||||
deleted_at: deleted_at,
|
||||
delete_order_id: delete_order_id,
|
||||
created_by: "test_runner",
|
||||
created_at: created_at ? created_at : new Date(),
|
||||
updated_by: "updater",
|
||||
updated_at: new Date(),
|
||||
});
|
||||
identifiers.pop() as LicenseArchive;
|
||||
};
|
||||
|
||||
export const createLicenseAllocationHistoryArchive = async (
|
||||
datasource: DataSource,
|
||||
id: number,
|
||||
user_id: number,
|
||||
license_id: number,
|
||||
is_allocated: boolean,
|
||||
account_id: number,
|
||||
executed_at: Date,
|
||||
switch_from_type: string
|
||||
): Promise<void> => {
|
||||
const { identifiers } = await datasource
|
||||
.getRepository(LicenseAllocationHistoryArchive)
|
||||
.insert({
|
||||
id: id,
|
||||
user_id: user_id,
|
||||
license_id: license_id,
|
||||
is_allocated: is_allocated,
|
||||
account_id: account_id,
|
||||
executed_at: executed_at,
|
||||
switch_from_type: switch_from_type,
|
||||
created_by: "test_runner",
|
||||
created_at: new Date(),
|
||||
updated_by: "updater",
|
||||
updated_at: new Date(),
|
||||
});
|
||||
identifiers.pop() as LicenseAllocationHistoryArchive;
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user