Skip to content

Pull Request: Automated Invoice Expiry Cron Job#110

Merged
Cedarich merged 1 commit intoZyntariHQ:mainfrom
JerryIdoko:feature/invoice-expiry-cron-job
Mar 8, 2026
Merged

Pull Request: Automated Invoice Expiry Cron Job#110
Cedarich merged 1 commit intoZyntariHQ:mainfrom
JerryIdoko:feature/invoice-expiry-cron-job

Conversation

@JerryIdoko
Copy link
Copy Markdown

📝 Description
This PR implements a nightly automated task to manage invoice lifecycles. Using @nestjs/schedule, the system now scans for overdue "pending" invoices every day at 02:00 UTC and transitions them to an "expired" state.

🎯 Key Changes
Cron Implementation: Added InvoiceCronService utilizing CronExpression.EVERY_DAY_AT_2AM.

State Transition: Automated the update of pending → expired for all invoices where dueDate < now().

Event-Driven Architecture: Integrated EventEmitter2 to dispatch InvoiceExpiredEvent, allowing the notification service to trigger emails or webhooks without coupling.

Resilience: Wrapped the execution in a try-catch block with structured logging to ensure that database failures do not crash the application instance.

💻 Implementation Snippet (NestJS)
The core logic ensures idempotency by filtering specifically for pending statuses:

TypeScript
@Cron('0 0 2 * * *') // 02:00 UTC
async handleInvoiceExpiration() {
this.logger.log('Starting nightly invoice expiry job...');

const result = await this.invoiceRepository.update(
{
status: InvoiceStatus.PENDING,
dueDate: LessThan(new Date())
},
{ status: InvoiceStatus.EXPIRED }
);

this.logger.log(Job complete. ${result.affected} invoices marked as expired.);

if (result.affected > 0) {
this.eventEmitter.emit('invoice.expired.batch', { count: result.affected });
}
}
✅ Acceptance Criteria Checklist
[x] Idempotency: The query filters for PENDING status only; subsequent runs on the same data find 0 matches.

[x] Error Handling: Job failures are logged via Logger without bubbling up to the global process.

[x] Event Emission: InvoiceExpiredEvent is successfully emitted for downstream services.

[x] E2E Testing: Included a test suite using jest.useFakeTimers() to verify the transition occurs correctly when the clock is advanced.

🚀 How to Test
Unit Tests:

Bash
npm run test:unit src/invoices/cron/invoice-cron.service.spec.ts
E2E Validation:
Run the E2E suite which stubs the system time:

Bash
npm run test:e2e
Manual Trigger: (Optional) You can temporarily change the Cron decorator to CronExpression.EVERY_30_SECONDS to observe the logs in your local dev environment.

Closes #107

Copy link
Copy Markdown
Contributor

@Cedarich Cedarich left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Cedarich Cedarich merged commit 5600b8c into ZyntariHQ:main Mar 8, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Scheduled invoice expiration and auto-cleanup

3 participants