diff --git a/dictation_function/src/entity/account.entity.ts b/dictation_function/src/entity/account.entity.ts index 3a1c9af..eed6190 100644 --- a/dictation_function/src/entity/account.entity.ts +++ b/dictation_function/src/entity/account.entity.ts @@ -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; } diff --git a/dictation_function/src/entity/license.entity.ts b/dictation_function/src/entity/license.entity.ts index 56f2f00..c4411f7 100644 --- a/dictation_function/src/entity/license.entity.ts +++ b/dictation_function/src/entity/license.entity.ts @@ -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; } diff --git a/dictation_function/src/entity/user.entity.ts b/dictation_function/src/entity/user.entity.ts index 10032d4..a63e9c0 100644 --- a/dictation_function/src/entity/user.entity.ts +++ b/dictation_function/src/entity/user.entity.ts @@ -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; } diff --git a/dictation_function/src/functions/analysisLicenses.ts b/dictation_function/src/functions/analysisLicenses.ts new file mode 100644 index 0000000..3d40276 --- /dev/null +++ b/dictation_function/src/functions/analysisLicenses.ts @@ -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 { + 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 { + 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 { + 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[]; +}; diff --git a/dictation_function/src/test/analysisLicenses.spec.ts b/dictation_function/src/test/analysisLicenses.spec.ts new file mode 100644 index 0000000..cd26bdb --- /dev/null +++ b/dictation_function/src/test/analysisLicenses.spec.ts @@ -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); + }); + +}); diff --git a/dictation_function/src/test/common/utility.ts b/dictation_function/src/test/common/utility.ts index f675ed7..a22eed7 100644 --- a/dictation_function/src/test/common/utility.ts +++ b/dictation_function/src/test/common/utility.ts @@ -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 => { 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 => { + 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 => { + 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 => { + 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 => { + 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; +};