Skip to content

Commit

Permalink
Scheduler E2E tests and accounts validation.
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewsignori-aot committed Feb 6, 2025
1 parent 3c4608b commit 2aacef8
Show file tree
Hide file tree
Showing 4 changed files with 245 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { INestApplication } from "@nestjs/common";
import {
createE2EDataSources,
createFakeCASInvoiceBatch,
createFakeDisbursementValue,
E2EDataSources,
saveFakeApplicationDisbursements,
saveFakeCASSupplier,
saveFakeDisbursementReceiptsFromDisbursementSchedule,
saveFakeInvoiceFromDisbursementReceipt,
saveFakeStudent,
} from "@sims/test-utils";
import { QueueNames } from "@sims/utilities";
Expand All @@ -19,6 +21,7 @@ import {
CASInvoiceBatchApprovalStatus,
CASInvoiceStatus,
DisbursementValueType,
OfferingIntensity,
SupplierStatus,
} from "@sims/sims-db";
import { SystemUsersService } from "@sims/services";
Expand Down Expand Up @@ -280,5 +283,202 @@ describe(
});
expect(currentInvoiceSequenceNumber.sequenceNumber).toEqual("2");
});

it("Should create a new CAS invoice and avoid creating a second invoice when a receipt has already has an invoice associated with.", async () => {
// Arrange
const casSupplier = await saveFakeCASSupplier(db, undefined, {
initialValues: {
supplierStatus: SupplierStatus.VerifiedManually,
},
});
const student = await saveFakeStudent(db.dataSource, { casSupplier });
// Application to have a new invoice associated with.
const applicationWithoutInvoice = await saveFakeApplicationDisbursements(
db.dataSource,
{
student,
disbursementValues: [
createFakeDisbursementValue(
DisbursementValueType.BCGrant,
"BCAG",
351,
{ effectiveAmount: 350 },
),
createFakeDisbursementValue(
DisbursementValueType.BCTotalGrant,
"BCSG",
351,
{ effectiveAmount: 350 },
),
],
},
);
const [firstDisbursementScheduleWithoutInvoice] =
applicationWithoutInvoice.currentAssessment.disbursementSchedules;
const { provincial: provincialDisbursementReceiptWithoutInvoice } =
await saveFakeDisbursementReceiptsFromDisbursementSchedule(
db,
firstDisbursementScheduleWithoutInvoice,
);
// Application to be skipped because already has an invoice associated with.
const applicationWithInvoice = await saveFakeApplicationDisbursements(
db.dataSource,
{
student,
disbursementValues: [
createFakeDisbursementValue(
DisbursementValueType.BCGrant,
"SBSD",
101,
{ effectiveAmount: 100 },
),
createFakeDisbursementValue(
DisbursementValueType.BCTotalGrant,
"BCSG",
101,
{ effectiveAmount: 100 },
),
],
},
);
const [firstDisbursementScheduleWithInvoice] =
applicationWithInvoice.currentAssessment.disbursementSchedules;
const { provincial: provincialDisbursementReceiptWithInvoice } =
await saveFakeDisbursementReceiptsFromDisbursementSchedule(
db,
firstDisbursementScheduleWithInvoice,
);
// Create invoice batch to be associated to the already generated invoice.
const casInvoiceBatch = await db.casInvoiceBatch.save(
createFakeCASInvoiceBatch({
creator: systemUsersService.systemUser,
}),
);
// Create invoice and its details associated with th batch
await saveFakeInvoiceFromDisbursementReceipt(db, {
casInvoiceBatch: casInvoiceBatch,
creator: systemUsersService.systemUser,
provincialDisbursementReceipt: provincialDisbursementReceiptWithInvoice,
casSupplier,
});

// Queued job.
const mockedJob = mockBullJob<void>();

// Act
const result = await processor.processQueue(mockedJob.job);

// Assert
expect(result).toStrictEqual([
"Batch created: SIMS-BATCH-1.",
"Invoices created: 1.",
]);
expect(
mockedJob.containLogMessages([
"Executing CAS invoices batches creation.",
"Checking for pending receipts.",
"Found 1 pending receipts.",
`Creating invoice for receipt ID ${provincialDisbursementReceiptWithoutInvoice.id}.`,
`Invoice SIMS-INVOICE-1-${casSupplier.supplierNumber} created for receipt ID ${provincialDisbursementReceiptWithoutInvoice.id}.`,
`Created invoice detail for award BCAG(CR).`,
`Created invoice detail for award BCAG(DR).`,
`CAS invoices batches creation process executed.`,
]),
).toBe(true);
expect(
mockedJob.containLogMessages([
`Creating invoice for receipt ID ${provincialDisbursementReceiptWithInvoice.id}.`,
]),
).toBe(false);
});

it("Should interrupt the process when an invoice is trying to be generated but there are no distribution accounts available to create the invoice details.", async () => {
// Arrange
const BC_GRANT_WITHOUT_DISTRIBUTION_ACCOUNT = "BCAG";
const casSupplier = await saveFakeCASSupplier(db, undefined, {
initialValues: {
supplierStatus: SupplierStatus.VerifiedManually,
},
});
const student = await saveFakeStudent(db.dataSource, { casSupplier });
const application = await saveFakeApplicationDisbursements(
db.dataSource,
{
student,
disbursementValues: [
createFakeDisbursementValue(
DisbursementValueType.BCGrant,
BC_GRANT_WITHOUT_DISTRIBUTION_ACCOUNT,
351,
{ effectiveAmount: 350 },
),
createFakeDisbursementValue(
DisbursementValueType.BCTotalGrant,
"BCSG",
351,
{ effectiveAmount: 350 },
),
],
},
);
const [firstDisbursementSchedule] =
application.currentAssessment.disbursementSchedules;
await saveFakeDisbursementReceiptsFromDisbursementSchedule(
db,
firstDisbursementSchedule,
);
// Change BCAG account to be inactive.
await db.casDistributionAccount.update(
{
awardValueCode: BC_GRANT_WITHOUT_DISTRIBUTION_ACCOUNT,
offeringIntensity: OfferingIntensity.fullTime,
},
{ isActive: false },
);

// Queued job.
const mockedJob = mockBullJob<void>();

// Act/Assert
await expect(processor.processQueue(mockedJob.job)).rejects.toThrow(
`No distribution accounts found for award ${BC_GRANT_WITHOUT_DISTRIBUTION_ACCOUNT} and offering intensity ${OfferingIntensity.fullTime}.`,
);

// Revert back the BCSG distribution account to be active.
await db.casDistributionAccount.update(
{
awardValueCode: BC_GRANT_WITHOUT_DISTRIBUTION_ACCOUNT,
offeringIntensity: OfferingIntensity.fullTime,
},
{ isActive: true },
);
});

it("Should finalize the process nicely when there is no pending receipt to process.", async () => {
// Arrange
// Queued job.
const mockedJob = mockBullJob<void>();

// Act
const result = await processor.processQueue(mockedJob.job);

// Assert
expect(result).toStrictEqual(["No batch was generated."]);
expect(
mockedJob.containLogMessages([
"Executing CAS invoices batches creation.",
"Checking for pending receipts.",
"No pending receipts found.",
"CAS invoices batches creation process executed.",
]),
).toBe(true);
const batchSequenceNumberExists = await db.sequenceControl.exists({
where: {
sequenceName: CAS_INVOICE_BATCH_SEQUENCE_NAME,
},
});
// Assert the sequence number was not created.
expect(batchSequenceNumberExists).toBe(false);
});
},
);
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { InjectQueue, Processor } from "@nestjs/bull";
import { QueueService } from "@sims/services/queue";
import { QueueNames } from "@sims/utilities";
import { CustomNamedError, QueueNames } from "@sims/utilities";
import {
InjectLogger,
LoggerService,
Expand All @@ -9,6 +9,7 @@ import {
import { Job, Queue } from "bull";
import { BaseScheduler } from "../base-scheduler";
import { CASInvoiceBatchService } from "../../../services";
import { DATABASE_TRANSACTION_CANCELLATION } from "@sims/services/constants";

/**
* Scheduler to generate batches for CAS invoices for e-Cert receipts.
Expand Down Expand Up @@ -36,17 +37,25 @@ export class CASInvoicesBatchesCreationScheduler extends BaseScheduler<void> {
processSummary: ProcessSummary,
): Promise<string[] | string> {
processSummary.info("Executing CAS invoices batches creation.");
const createdBatch = await this.casInvoiceBatchService.createInvoiceBatch(
processSummary,
);
processSummary.info("CAS invoices batches creation process executed.");
if (!createdBatch) {
return "No batch was generated.";
try {
const createdBatch = await this.casInvoiceBatchService.createInvoiceBatch(
processSummary,
);
return [
`Batch created: ${createdBatch.batchName}.`,
`Invoices created: ${createdBatch.casInvoices.length}.`,
];
} catch (error: unknown) {
if (
error instanceof CustomNamedError &&
error.name === DATABASE_TRANSACTION_CANCELLATION
) {
return "No batch was generated.";
}
throw error;
} finally {
processSummary.info("CAS invoices batches creation process executed.");
}
return [
`Batch created: ${createdBatch.batchName}.`,
`Invoices created: ${createdBatch.casInvoices.length}.`,
];
}

@InjectLogger()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { Injectable } from "@nestjs/common";
import { BC_TOTAL_GRANT_AWARD_CODE } from "@sims/services/constants";
import {
BC_TOTAL_GRANT_AWARD_CODE,
DATABASE_TRANSACTION_CANCELLATION,
} from "@sims/services/constants";
import {
CASDistributionAccount,
CASInvoice,
Expand All @@ -15,6 +18,7 @@ import {
import { DataSource, EntityManager } from "typeorm";
import { SequenceControlService, SystemUsersService } from "@sims/services";
import { ProcessSummary } from "@sims/utilities/logger";
import { CustomNamedError } from "@sims/utilities";

/**
* Chunk size for inserting invoices. The invoices (and its details) will
Expand Down Expand Up @@ -57,8 +61,10 @@ export class CASInvoiceBatchService {
if (!pendingReceipts.length) {
processSummary.info("No pending receipts found.");
// Cancels the transaction to rollback the batch number.
await entityManager.queryRunner.rollbackTransaction();
return null;
throw new CustomNamedError(
"Rollback current database for invoice batch processing.",
DATABASE_TRANSACTION_CANCELLATION,
);
}
processSummary.info(`Found ${pendingReceipts.length} pending receipts.`);
const now = new Date();
Expand Down Expand Up @@ -193,6 +199,11 @@ export class CASInvoiceBatchService {
account.awardValueCode === disbursedAward.valueCode &&
account.offeringIntensity === offeringIntensity,
);
if (!accounts.length) {
throw new Error(
`No distribution accounts found for award ${disbursedAward.valueCode} and offering intensity ${offeringIntensity}.`,
);
}
// Create invoice details for each distribution account.
const awardInvoiceDetails = accounts.map((account) => {
const invoiceDetail = new CASInvoiceDetail();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,14 @@ export const UNEXPECTED_ERROR_DOWNLOADING_FILE =
export const ECE_DISBURSEMENT_DATA_NOT_VALID =
"ECE_DISBURSEMENT_DATA_NOT_VALID";
export const FILE_PARSING_ERROR = "FILE_PARSING_ERROR";

/**
* Used to cancel a database transaction started using the method as below.
* @example
* await myDataSource.transaction(async (transactionalEntityManager) => {
* // execute queries using transactionalEntityManager
* })
* @see https://typeorm.io/#/transactions
*/
export const DATABASE_TRANSACTION_CANCELLATION =
"DATABASE_TRANSACTION_CANCELLATION";

0 comments on commit 2aacef8

Please sign in to comment.