You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Core]: The user shall be able to produce a complete Income vs Expenses report for a given period of time #71
Per financial year. or every three or six months, according to the needs of the company
Specific:
The system should generate financial reports at predefined intervals.
Measurable:
Report generation frequency can be configured to occur either annually, bi-annually, or quarterly.
Achievable:
The existing reporting module can be extended with configuration options to support different reporting intervals.
Relevant:
Regular financial reports are essential for effective business performance monitoring and decision-making.
Time-bound:
This functionality will be implemented and deployed within the next sprint (2 weeks).
### Arrange:
// Create a configuration setting for report generation frequency.
// Define the possible values as 'annually', 'bi-annually', and 'quarterly'.
// Set the initial report generation frequency to 'annually'.
### Act:
// Trigger the report generation process.
// The system should determine the current time and compare it against the configured report generation frequency.
// If the current time aligns with the scheduled interval (e.g., end of the year for 'annually'), a report should be generated.
### Assert:
// Verify that a financial report file was created.
// Verify that the report file contains the expected data based on the report generation frequency and reporting period (e.g., data for the entire year if 'annually').
// Verify that the report generation timestamp is recorded.
Implement automated financial report generation that produces Income vs Expenses summaries at configurable intervals (annually, bi-annually, quarterly), with scheduled execution, file storage, and administrative management interface. The solution will create a new FinancialReports module following the existing Laravel modular architecture, leveraging established patterns for exports, background jobs, and Filament admin interfaces.
Observations
This Laravel application uses a modular architecture with business logic organized into Modules/ directories. The system has established patterns for export services (CSV/XLSX via Maatwebsite/Excel), background jobs (database queue), console commands, and Filament admin resources. Financial data comes from the Invoices, Payments, and Expenses modules. A ReportBuilder module exists but is designed for document templates (invoices/quotes) rather than aggregated financial summaries. The scheduler infrastructure is prepared but currently inactive across service providers.
AssumptionsAssumption 1: Income Calculation Method
Options Considered:
Use invoice totals - represents billed amounts regardless of payment status
Use payment amounts - represents actual cash received during the period
Support both as separate line items in the report
Chosen Option: 3
Rationale: Include both invoice totals and payment amounts as separate metrics, giving users a complete picture of billed vs received income.
Assumption 2: Report Scope
Options Considered:
System-wide reports aggregating all companies
Per-company reports with company-scoped data
Configurable per schedule
Chosen Option: 2
Rationale: Per-company reports, aligning with the existing multi-tenant architecture where all financial entities are company-scoped.
Assumption 3: Schedule Storage
Options Considered:
Add frequency columns to existing report_templates table
Create dedicated report_schedules table
Use the global settings table with JSON configuration
Chosen Option: 2
Rationale: Dedicated report_schedules table provides clear separation between template design (ReportBuilder) and automated generation (FinancialReports), supports multiple active schedules per company, and follows the modular pattern.
💡 User Tips
Regenerate the plan with different choices with @coderabbitai <feedback>.
Implementation Steps
Phase 1: Module Foundation & Data Structure
Establish the FinancialReports module with database schema for storing report schedules and generated report metadata.
Task 1: Create FinancialReports Module Structure
Create the module foundation with standard Laravel module directories following the existing modular pattern.
Generate module structure in Modules/FinancialReports/ with subdirectories: Models/, Services/, DTOs/, Enums/, Jobs/, Console/, Filament/, Database/Migrations/, Tests/
Create module service provider in Modules/FinancialReports/Providers/FinancialReportsServiceProvider.php to register routes, migrations, and translations
Register the service provider in config/modules.php
Task 2: Create Database Schema for Report Schedules
Define the schema for storing report generation frequency configuration per company.
Create migration in Modules/FinancialReports/Database/Migrations/ for financial_report_schedules table
Include columns: id, company_id (foreign key), frequency (enum: annually, bi-annually, quarterly), is_active (boolean), last_generated_at (timestamp), next_scheduled_at (timestamp), timestamps
Add unique constraint on company_id to enforce one active schedule per company
Add index on next_scheduled_at for efficient schedule checking
Task 3: Create Database Schema for Generated Reports
Define the schema for tracking generated report files and metadata.
Create migration in Modules/FinancialReports/Database/Migrations/ for financial_reports table
Add indexes on company_id and generated_at for efficient querying
Store file paths relative to the storage disk for portability
Task 4: Create Models and Enums
Implement Eloquent models and supporting enums following existing codebase patterns.
Create Modules/FinancialReports/Models/FinancialReportSchedule.php with relationships to Company and FinancialReports
Create Modules/FinancialReports/Models/FinancialReport.php with relationships to Company and FinancialReportSchedule
Create Modules/FinancialReports/Enums/ReportFrequency.php enum with cases: Annually, BiAnnually, Quarterly
Create Modules/FinancialReports/Enums/FinancialReportType.php enum with case: IncomeVsExpenses (extensible for future report types)
Add scope methods to FinancialReportSchedule for finding schedules due for generation
🤖 Prompt for AI agents
Set up the FinancialReports module foundation with database schema for report
schedules and generated reports. Create the module structure, service provider,
and database migrations.
Create module structure:
- Generate `Modules/FinancialReports/` directory with subdirectories: `Models/`,
`Services/`, `DTOs/`, `Enums/`, `Jobs/`, `Console/`, `Filament/`,
`Database/Migrations/`, `Tests/`
- Create `FinancialReportsServiceProvider.php` in `Providers/` subdirectory to
register migrations and translations
- Register provider in `config/modules.php`
Create database migrations:
- `financial_report_schedules` table with: `id`, `company_id` (FK), `frequency`
(enum), `is_active` (boolean), `last_generated_at`, `next_scheduled_at`,
timestamps
- Add unique constraint on `company_id` and index on `next_scheduled_at`
- `financial_reports` table with: `id`, `company_id` (FK), `schedule_id` (FK
nullable), `report_type` (enum), `period_start_date`, `period_end_date`,
`file_path`, `file_format` (enum), `generated_at`, timestamps
- Add indexes on `company_id` and `generated_at`
Create models and enums:
- `FinancialReportSchedule` model with Company and FinancialReport
relationships, plus scope for due schedules
- `FinancialReport` model with Company and FinancialReportSchedule relationships
- `ReportFrequency` enum with Annually, BiAnnually, Quarterly cases
- `FinancialReportType` enum with IncomeVsExpenses case
Phase 2: Financial Data Aggregation Services
Build services to query and aggregate income and expense data from existing modules for specified date ranges.
Task 1: Create Financial Data Aggregation Service
Implement service to aggregate income and expense data from Invoices, Payments, and Expenses modules.
Implement method to calculate total invoiced amount for period by querying Invoices module (filter by invoiced_at date and company_id, sum invoice_total)
Implement method to calculate total payments received for period by querying Payments module (filter by paid_at date and company_id, sum payment_amount)
Implement method to calculate total expenses for period by querying Expenses module (filter by expensed_at date and company_id, sum expense_amount)
Group expenses by category and return category breakdowns
Handle edge cases: empty results, null dates, company without data
Task 2: Create Date Range Calculator
Implement service to calculate period start/end dates based on report frequency.
Include properties: period_start, period_end, total_invoiced, total_payments_received, total_expenses, expense_breakdown (array of category => amount), net_income (calculated)
Implement calculation method for net_income (total_payments_received - total_expenses)
Add formatting methods for currency display using the FormatsCurrency trait pattern
Include metadata: company_id, generated_at, currency_code
🤖 Prompt for AI agents
Build services to aggregate financial data from Invoices, Payments, and Expenses
modules for specified date ranges. Create period calculator and data transfer
object.
Create FinancialDataAggregator service:
- Query Invoices by `invoiced_at` date and `company_id`, sum `invoice_total`
- Query Payments by `paid_at` date and `company_id`, sum `payment_amount`
- Query Expenses by `expensed_at` date and `company_id`, sum `expense_amount`
- Group expenses by category for breakdown
- Handle empty results and null dates
Create ReportPeriodCalculator service:
- Calculate period dates for annually (fiscal year), bi-annually (6 months),
quarterly (3 months)
- Use company fiscal year settings or default to calendar year
- Return Carbon instances for start/end dates
- Handle leap years and partial periods
Create FinancialSummaryDTO:
- Properties: `period_start`, `period_end`, `total_invoiced`,
`total_payments_received`, `total_expenses`, `expense_breakdown`, `net_income`
- Calculate net income as total_payments_received minus total_expenses
- Add currency formatting methods using FormatsCurrency trait
- Include metadata: `company_id`, `generated_at`, `currency_code`
Phase 3: Report File Generation
Create services to generate report files in PDF and Excel formats, storing them in the filesystem.
Task 1: Create Report File Generator Service
Implement service to transform financial summary data into downloadable files.
Accept FinancialReportSchedule model and optional format parameter in constructor
In handle() method: calculate period dates, aggregate data via FinancialDataAggregator, generate file via FinancialReportGenerator, create FinancialReport record
Update schedule's last_generated_at and next_scheduled_at timestamps
Implement retry logic with exponential backoff following Modules/Invoices/Jobs/Peppol/SendInvoiceToPeppolJob.php pattern
Add proper error handling and logging for failed generations
🤖 Prompt for AI agents
Create services to generate PDF and Excel reports from financial data and store
them in the filesystem. Build background job for asynchronous generation.
Create FinancialReportGenerator service:
- Follow patterns from `InvoiceExportService.php`
- Implement `generatePDF()` using DomPDF or similar library
- Implement `generateExcel()` using Maatwebsite/Excel with summary and breakdown
sheets
- Store files in `storage/app/financial_reports/` using Storage facade
- Return file path for database storage
- Add error handling and logging
Create FinancialSummaryExport class:
- Implement FromCollection and WithHeadings interfaces
- Structure with summary section (period, income, expenses, net) and expense
breakdown by category
- Follow existing export formatting patterns
- Add currency formatting and column widths
Create GenerateFinancialReportJob:
- Implement ShouldQueue interface
- Accept FinancialReportSchedule and optional format parameter
- In `handle()`: calculate dates, aggregate data, generate file, create
FinancialReport record
- Update schedule timestamps: `last_generated_at` and `next_scheduled_at`
- Implement retry logic with exponential backoff following existing job patterns
- Add error handling and logging
Phase 4: Scheduling & Automation
Implement console command and activate Laravel scheduler for automated report generation.
Task 1: Create Report Generation Console Command
Build console command to check schedules and trigger report generation.
Implement signature 'financial-reports:generate-scheduled' with optional --force flag
Query FinancialReportSchedule model for schedules where next_scheduled_at <= now() and is_active = true
Dispatch GenerateFinancialReportJob for each due schedule
Include dry-run option to preview what would be generated without executing
Add verbose output for logging which schedules are being processed
Task 2: Activate Laravel Task Scheduler
Enable the Laravel scheduler in the service provider to run the command automatically.
Uncomment and configure the scheduler callback in Modules/FinancialReports/Providers/FinancialReportsServiceProvider.php
Schedule the GenerateScheduledReportsCommand to run daily at a configured time (e.g., 2 AM)
Follow the commented-out scheduler pattern found in existing service providers
Add scheduler registration to the boot() method of the provider
Document that the Laravel scheduler cron job must be configured on the server (* * * * * php artisan schedule:run)
Task 3: Implement Schedule Calculation Logic
Add methods to calculate next scheduled run date based on frequency.
Add calculateNextScheduledDate() method to FinancialReportSchedule model or create dedicated service
For Annually: add 1 year to last_generated_at (or use fiscal year end date)
For BiAnnually: add 6 months to last_generated_at
For Quarterly: add 3 months to last_generated_at
Handle initial scheduling when last_generated_at is null (set to next period end based on current date)
Consider timezone handling based on company settings or default to application timezone
🤖 Prompt for AI agents
Implement console command for scheduled report generation and activate Laravel
scheduler. Add schedule calculation logic.
Create GenerateScheduledReportsCommand:
- Signature: `financial-reports:generate-scheduled` with optional `--force` flag
- Query FinancialReportSchedule where `next_scheduled_at <= now()` and
`is_active = true`
- Dispatch GenerateFinancialReportJob for each due schedule
- Add dry-run option to preview without executing
- Include verbose output for logging
Activate Laravel scheduler:
- Configure scheduler callback in `FinancialReportsServiceProvider.php`
- Schedule command to run daily at 2 AM
- Follow existing scheduler patterns from other service providers
- Register in provider's `boot()` method
- Document cron job requirement: `* * * * * php artisan schedule:run`
Implement schedule calculation:
- Add `calculateNextScheduledDate()` method to FinancialReportSchedule model
- Annually: add 1 year to `last_generated_at` or use fiscal year end
- BiAnnually: add 6 months
- Quarterly: add 3 months
- Handle null `last_generated_at` by setting to next period end from current
date
- Use company timezone or application default
Phase 5: Administrative Interface
Create Filament resources for managing report schedules and viewing/downloading generated reports.
Implement table displaying reports with columns: report type, period, format, generated date
Add download action following Filament's downloadable action pattern using Storage::download()
Add bulk delete action for old reports
Add filter by period (date range) and format
Include summary card showing total reports generated and storage usage
Set navigation group to 'Reports'
Task 3: Add Manual Report Generation Action
Create action to generate reports on-demand from the admin interface.
In FinancialReportScheduleResource, add custom "Generate Now" action in table or view page
Action should open modal to select report format (PDF/Excel) and optionally override date range
Dispatch GenerateFinancialReportJob with custom parameters
Show success notification when job is dispatched
Follow Filament action patterns from existing resources
🤖 Prompt for AI agents
Create Filament admin resources for managing report schedules and viewing
generated reports. Add manual generation action.
Create FinancialReportScheduleResource:
- Form fields: frequency (select), is_active (toggle), next_scheduled_at
(read-only)
- Validation to restrict one schedule per company
- Table columns: frequency, status, last generated, next scheduled
- Custom action to manually trigger report generation
- Set navigation group to 'Reports'
Create FinancialReportResource:
- Table columns: report type, period, format, generated date
- Download action using `Storage::download()`
- Bulk delete action for old reports
- Filters by period (date range) and format
- Summary card showing total reports and storage usage
- Set navigation group to 'Reports'
Add manual generation action:
- "Generate Now" action in FinancialReportScheduleResource table/view page
- Modal to select format (PDF/Excel) and optional date range override
- Dispatch GenerateFinancialReportJob with custom parameters
- Success notification on dispatch
- Follow Filament action patterns
Phase 6: Testing
Implement comprehensive PHPUnit tests covering data aggregation, report generation, scheduling logic, and edge cases.
Task 1: Unit Tests for Data Aggregation
Test financial data calculation accuracy and edge cases.
Test income calculation from invoices (Arrange: create invoices with various totals, Act: aggregate for period, Assert: correct sum)
Test payment aggregation (Arrange: create payments across date range, Act: aggregate, Assert: correct sum)
Test expense aggregation with category breakdown (Arrange: create expenses in multiple categories, Act: aggregate, Assert: correct totals and breakdown)
Test edge case: no data in period (Assert: zero totals returned)
Test edge case: date boundary conditions (data exactly on period start/end dates)
Test filtering by company_id (Assert: only company's data included)
Follow AAA pattern with #[Test] and #[Group('unit')] attributes
Task 2: Unit Tests for Period Calculation
Test date range calculations for different frequencies.
Test report generation for brand new company with no financial data (Assert: valid report with zero totals)
Test handling of missing or null date values in source data
Test currency handling with mixed currencies (if supported) or single currency enforcement
Test storage disk failures (Assert: proper error handling and logging)
Test concurrent job execution (Assert: no race conditions in schedule updates)
Test timezone edge cases (Assert: correct period calculation across timezone boundaries)
Test database transaction rollback on partial failures
🤖 Prompt for AI agents
Implement comprehensive PHPUnit tests for data aggregation, report generation,
scheduling, and edge cases.
Create FinancialDataAggregatorTest:
- Test invoice income calculation with various totals
- Test payment aggregation across date ranges
- Test expense aggregation with category breakdowns
- Test edge cases: no data, date boundaries, company filtering
- Use AAA pattern with #[Test] and #[Group('unit')] attributes
Create ReportPeriodCalculatorTest:
- Test annual, bi-annual, and quarterly period calculations
- Test leap year handling
- Test fiscal vs calendar year differences
- Test next_scheduled_date calculation
- Use `Carbon::setTestNow()` for time control
Create GenerateFinancialReportTest:
- Test complete report generation workflow
- Test PDF and Excel format generation
- Test schedule timestamp updates
- Test job retry on failure
- Use feature test base and database transactions
Create ScheduledReportGenerationTest:
- Test command execution with due schedules
- Test filtering: only due and active schedules processed
- Test multiple companies
- Test dry-run mode
- Use `Artisan::call()` to execute command
Create FinancialReportEdgeCasesTest:
- Test new company with no data
- Test null date handling
- Test currency handling
- Test storage failures
- Test concurrent execution and race conditions
- Test timezone edge cases
- Test transaction rollbacks
🚀 Next Steps
🤖 All AI agent prompts combined
Task: 1
Set up the FinancialReports module foundation with database schema for report
schedules and generated reports. Create the module structure, service provider,
and database migrations.
Create module structure:
- Generate `Modules/FinancialReports/` directory with subdirectories: `Models/`,
`Services/`, `DTOs/`, `Enums/`, `Jobs/`, `Console/`, `Filament/`,
`Database/Migrations/`, `Tests/`
- Create `FinancialReportsServiceProvider.php` in `Providers/` subdirectory to
register migrations and translations
- Register provider in `config/modules.php`
Create database migrations:
- `financial_report_schedules` table with: `id`, `company_id` (FK), `frequency`
(enum), `is_active` (boolean), `last_generated_at`, `next_scheduled_at`,
timestamps
- Add unique constraint on `company_id` and index on `next_scheduled_at`
- `financial_reports` table with: `id`, `company_id` (FK), `schedule_id` (FK
nullable), `report_type` (enum), `period_start_date`, `period_end_date`,
`file_path`, `file_format` (enum), `generated_at`, timestamps
- Add indexes on `company_id` and `generated_at`
Create models and enums:
- `FinancialReportSchedule` model with Company and FinancialReport
relationships, plus scope for due schedules
- `FinancialReport` model with Company and FinancialReportSchedule relationships
- `ReportFrequency` enum with Annually, BiAnnually, Quarterly cases
- `FinancialReportType` enum with IncomeVsExpenses case
===============================================================================
Task: 2
Build services to aggregate financial data from Invoices, Payments, and Expenses
modules for specified date ranges. Create period calculator and data transfer
object.
Create FinancialDataAggregator service:
- Query Invoices by `invoiced_at` date and `company_id`, sum `invoice_total`
- Query Payments by `paid_at` date and `company_id`, sum `payment_amount`
- Query Expenses by `expensed_at` date and `company_id`, sum `expense_amount`
- Group expenses by category for breakdown
- Handle empty results and null dates
Create ReportPeriodCalculator service:
- Calculate period dates for annually (fiscal year), bi-annually (6 months),
quarterly (3 months)
- Use company fiscal year settings or default to calendar year
- Return Carbon instances for start/end dates
- Handle leap years and partial periods
Create FinancialSummaryDTO:
- Properties: `period_start`, `period_end`, `total_invoiced`,
`total_payments_received`, `total_expenses`, `expense_breakdown`, `net_income`
- Calculate net income as total_payments_received minus total_expenses
- Add currency formatting methods using FormatsCurrency trait
- Include metadata: `company_id`, `generated_at`, `currency_code`
===============================================================================
Task: 3
Create services to generate PDF and Excel reports from financial data and store
them in the filesystem. Build background job for asynchronous generation.
Create FinancialReportGenerator service:
- Follow patterns from `InvoiceExportService.php`
- Implement `generatePDF()` using DomPDF or similar library
- Implement `generateExcel()` using Maatwebsite/Excel with summary and breakdown
sheets
- Store files in `storage/app/financial_reports/` using Storage facade
- Return file path for database storage
- Add error handling and logging
Create FinancialSummaryExport class:
- Implement FromCollection and WithHeadings interfaces
- Structure with summary section (period, income, expenses, net) and expense
breakdown by category
- Follow existing export formatting patterns
- Add currency formatting and column widths
Create GenerateFinancialReportJob:
- Implement ShouldQueue interface
- Accept FinancialReportSchedule and optional format parameter
- In `handle()`: calculate dates, aggregate data, generate file, create
FinancialReport record
- Update schedule timestamps: `last_generated_at` and `next_scheduled_at`
- Implement retry logic with exponential backoff following existing job patterns
- Add error handling and logging
===============================================================================
Task: 4
Implement console command for scheduled report generation and activate Laravel
scheduler. Add schedule calculation logic.
Create GenerateScheduledReportsCommand:
- Signature: `financial-reports:generate-scheduled` with optional `--force` flag
- Query FinancialReportSchedule where `next_scheduled_at <= now()` and
`is_active = true`
- Dispatch GenerateFinancialReportJob for each due schedule
- Add dry-run option to preview without executing
- Include verbose output for logging
Activate Laravel scheduler:
- Configure scheduler callback in `FinancialReportsServiceProvider.php`
- Schedule command to run daily at 2 AM
- Follow existing scheduler patterns from other service providers
- Register in provider's `boot()` method
- Document cron job requirement: `* * * * * php artisan schedule:run`
Implement schedule calculation:
- Add `calculateNextScheduledDate()` method to FinancialReportSchedule model
- Annually: add 1 year to `last_generated_at` or use fiscal year end
- BiAnnually: add 6 months
- Quarterly: add 3 months
- Handle null `last_generated_at` by setting to next period end from current
date
- Use company timezone or application default
===============================================================================
Task: 5
Create Filament admin resources for managing report schedules and viewing
generated reports. Add manual generation action.
Create FinancialReportScheduleResource:
- Form fields: frequency (select), is_active (toggle), next_scheduled_at
(read-only)
- Validation to restrict one schedule per company
- Table columns: frequency, status, last generated, next scheduled
- Custom action to manually trigger report generation
- Set navigation group to 'Reports'
Create FinancialReportResource:
- Table columns: report type, period, format, generated date
- Download action using `Storage::download()`
- Bulk delete action for old reports
- Filters by period (date range) and format
- Summary card showing total reports and storage usage
- Set navigation group to 'Reports'
Add manual generation action:
- "Generate Now" action in FinancialReportScheduleResource table/view page
- Modal to select format (PDF/Excel) and optional date range override
- Dispatch GenerateFinancialReportJob with custom parameters
- Success notification on dispatch
- Follow Filament action patterns
===============================================================================
Task: 6
Implement comprehensive PHPUnit tests for data aggregation, report generation,
scheduling, and edge cases.
Create FinancialDataAggregatorTest:
- Test invoice income calculation with various totals
- Test payment aggregation across date ranges
- Test expense aggregation with category breakdowns
- Test edge cases: no data, date boundaries, company filtering
- Use AAA pattern with #[Test] and #[Group('unit')] attributes
Create ReportPeriodCalculatorTest:
- Test annual, bi-annual, and quarterly period calculations
- Test leap year handling
- Test fiscal vs calendar year differences
- Test next_scheduled_date calculation
- Use `Carbon::setTestNow()` for time control
Create GenerateFinancialReportTest:
- Test complete report generation workflow
- Test PDF and Excel format generation
- Test schedule timestamp updates
- Test job retry on failure
- Use feature test base and database transactions
Create ScheduledReportGenerationTest:
- Test command execution with due schedules
- Test filtering: only due and active schedules processed
- Test multiple companies
- Test dry-run mode
- Use `Artisan::call()` to execute command
Create FinancialReportEdgeCasesTest:
- Test new company with no data
- Test null date handling
- Test currency handling
- Test storage failures
- Test concurrent execution and race conditions
- Test timezone edge cases
- Test transaction rollbacks
💡 Iterate on the plan with:
@coderabbitai <feedback>
Example Feedback
- @coderabbitai You can skip phase 3. Add a simple unit test case for phase 2.
- @coderabbitai For assumption 1 go ahead with option 3 and replan.
💬 Have feedback or questions? Drop into our discord!
Let a company generate a financial summary report (total income from invoices/payments vs. total expenses) for a configurable period — annually, bi-annually, or quarterly. Current state: Modules\Core\Enums\ReportDataSource already lists EXPENSE and INVOICE as report data sources, and ReportFieldService exposes their available fields (getFieldsBySource()), but this only feeds a generic report-template designer page. No aggregation service and no scheduling/period configuration for an Income vs. Expenses summary exists yet — this needs new code, not just wiring into the designer (the designer's ReportTemplateService class referenced by its Blade view doesn't currently exist in the codebase either, so it isn't a safe integration point yet).
Measurable
A company can set a report interval (annually, bi-annually, quarterly) in company settings.
IncomeExpenseReportService::generate($company, $from, $to) sums invoice/payment income and expense totals within the given period, scoped to the company.
A report can be triggered on demand from the UI and downloaded/rendered without waiting for the schedule.
Frontend/Livewire: As a company admin I want to generate the Income vs Expenses report on demand so that I don't have to wait for the next scheduled interval.
New IncomeExpenseReportService in Modules/Core/Services/ with a generate(Company $company, Carbon $from, Carbon $to): IncomeExpenseReportData method, querying Payment (income) and Expense (expenses), both already scoped via BelongsToCompany.
Add income_expense_report_interval to company settings (migration + Filament settings form field), reusing the existing Settings page pattern.
Build a dedicated Filament page (not the incomplete report-template designer) that calls the service on demand and renders totals; this is a new page rather than the broken ReportTemplateService integration.
If scheduled generation is required (not just on-demand), add a console command + scheduler entry that runs per the configured interval and stores/exports the result (format — email, stored PDF — to be confirmed with the reporter).
Add PHPUnit coverage in Modules/Core/Tests/Feature/.
Relevant
Company admins need periodic visibility into profitability without manually cross-referencing invoices, payments, and expenses — this is core financial reporting, not a nice-to-have.
Time-bound
Sprint 7 (report interval/output format to be confirmed with the original reporter before implementation starts).
Activity
nielsdrost7 commentedon Jan 2, 2026
@coderabbitai make pull-request with AAA test Scenario's: Arrange, Act, Assert. And also make sure you test edge cases
coderabbitai commentedon Jan 2, 2026
Coding Plan
Approach
Implement automated financial report generation that produces Income vs Expenses summaries at configurable intervals (annually, bi-annually, quarterly), with scheduled execution, file storage, and administrative management interface. The solution will create a new FinancialReports module following the existing Laravel modular architecture, leveraging established patterns for exports, background jobs, and Filament admin interfaces.
Observations
This Laravel application uses a modular architecture with business logic organized into Modules/ directories. The system has established patterns for export services (CSV/XLSX via Maatwebsite/Excel), background jobs (database queue), console commands, and Filament admin resources. Financial data comes from the Invoices, Payments, and Expenses modules. A ReportBuilder module exists but is designed for document templates (invoices/quotes) rather than aggregated financial summaries. The scheduler infrastructure is prepared but currently inactive across service providers.
Assumptions
Assumption 1: Income Calculation Method
Options Considered:
Chosen Option: 3
Rationale: Include both invoice totals and payment amounts as separate metrics, giving users a complete picture of billed vs received income.
Assumption 2: Report Scope
Options Considered:
Chosen Option: 2
Rationale: Per-company reports, aligning with the existing multi-tenant architecture where all financial entities are company-scoped.
Assumption 3: Schedule Storage
Options Considered:
Chosen Option: 2
Rationale: Dedicated report_schedules table provides clear separation between template design (ReportBuilder) and automated generation (FinancialReports), supports multiple active schedules per company, and follows the modular pattern.
💡 User Tips
Regenerate the plan with different choices with
@coderabbitai <feedback>.Implementation Steps
Phase 1: Module Foundation & Data Structure
Establish the FinancialReports module with database schema for storing report schedules and generated report metadata.
Task 1: Create FinancialReports Module Structure
Create the module foundation with standard Laravel module directories following the existing modular pattern.
Modules/FinancialReports/with subdirectories:Models/,Services/,DTOs/,Enums/,Jobs/,Console/,Filament/,Database/Migrations/,Tests/Modules/FinancialReports/Providers/FinancialReportsServiceProvider.phpto register routes, migrations, and translationsconfig/modules.phpTask 2: Create Database Schema for Report Schedules
Define the schema for storing report generation frequency configuration per company.
Modules/FinancialReports/Database/Migrations/forfinancial_report_schedulestableid,company_id(foreign key),frequency(enum: annually, bi-annually, quarterly),is_active(boolean),last_generated_at(timestamp),next_scheduled_at(timestamp),timestampscompany_idto enforce one active schedule per companynext_scheduled_atfor efficient schedule checkingTask 3: Create Database Schema for Generated Reports
Define the schema for tracking generated report files and metadata.
Modules/FinancialReports/Database/Migrations/forfinancial_reportstableid,company_id(foreign key),schedule_id(foreign key, nullable),report_type(enum: income_vs_expenses),period_start_date,period_end_date,file_path,file_format(enum: pdf, xlsx),generated_at(timestamp),timestampscompany_idandgenerated_atfor efficient queryingTask 4: Create Models and Enums
Implement Eloquent models and supporting enums following existing codebase patterns.
Modules/FinancialReports/Models/FinancialReportSchedule.phpwith relationships to Company and FinancialReportsModules/FinancialReports/Models/FinancialReport.phpwith relationships to Company and FinancialReportScheduleModules/FinancialReports/Enums/ReportFrequency.phpenum with cases: Annually, BiAnnually, QuarterlyModules/FinancialReports/Enums/FinancialReportType.phpenum with case: IncomeVsExpenses (extensible for future report types)🤖 Prompt for AI agents
Phase 2: Financial Data Aggregation Services
Build services to query and aggregate income and expense data from existing modules for specified date ranges.
Task 1: Create Financial Data Aggregation Service
Implement service to aggregate income and expense data from Invoices, Payments, and Expenses modules.
Modules/FinancialReports/Services/FinancialDataAggregator.phpinvoiced_atdate andcompany_id, suminvoice_total)paid_atdate andcompany_id, sumpayment_amount)expensed_atdate andcompany_id, sumexpense_amount)Task 2: Create Date Range Calculator
Implement service to calculate period start/end dates based on report frequency.
Modules/FinancialReports/Services/ReportPeriodCalculator.phpTask 3: Create Financial Summary DTO
Define data transfer object to structure aggregated financial data.
Modules/FinancialReports/DTOs/FinancialSummaryDTO.phpperiod_start,period_end,total_invoiced,total_payments_received,total_expenses,expense_breakdown(array of category => amount),net_income(calculated)net_income(total_payments_received - total_expenses)company_id,generated_at,currency_code🤖 Prompt for AI agents
Phase 3: Report File Generation
Create services to generate report files in PDF and Excel formats, storing them in the filesystem.
Task 1: Create Report File Generator Service
Implement service to transform financial summary data into downloadable files.
Modules/FinancialReports/Services/FinancialReportGenerator.phpModules/Invoices/Services/InvoiceExportService.phpgeneratePDF()method that creates formatted PDF using existing libraries (consider using Laravel DomPDF or similar)generateExcel()method that creates XLSX file using Maatwebsite/Excel with summary sheet and expense breakdown sheetstorage/app/financial_reports/using Storage facadeTask 2: Create Excel Export Class
Build Excel export class for structured financial data following Maatwebsite/Excel patterns.
Modules/FinancialReports/Exports/FinancialSummaryExport.phpimplementing FromCollection and WithHeadings interfacesTask 3: Create Background Job for Report Generation
Implement queueable job for asynchronous report generation following existing job patterns.
Modules/FinancialReports/Jobs/GenerateFinancialReportJob.phpimplementing ShouldQueuehandle()method: calculate period dates, aggregate data via FinancialDataAggregator, generate file via FinancialReportGenerator, create FinancialReport recordlast_generated_atandnext_scheduled_attimestampsModules/Invoices/Jobs/Peppol/SendInvoiceToPeppolJob.phppattern🤖 Prompt for AI agents
Phase 4: Scheduling & Automation
Implement console command and activate Laravel scheduler for automated report generation.
Task 1: Create Report Generation Console Command
Build console command to check schedules and trigger report generation.
Modules/FinancialReports/Console/Commands/GenerateScheduledReportsCommand.phpnext_scheduled_at <= now()andis_active = trueTask 2: Activate Laravel Task Scheduler
Enable the Laravel scheduler in the service provider to run the command automatically.
Modules/FinancialReports/Providers/FinancialReportsServiceProvider.phpboot()method of the provider* * * * * php artisan schedule:run)Task 3: Implement Schedule Calculation Logic
Add methods to calculate next scheduled run date based on frequency.
calculateNextScheduledDate()method to FinancialReportSchedule model or create dedicated servicelast_generated_at(or use fiscal year end date)last_generated_atlast_generated_atlast_generated_atis null (set to next period end based on current date)🤖 Prompt for AI agents
Phase 5: Administrative Interface
Create Filament resources for managing report schedules and viewing/downloading generated reports.
Task 1: Create Report Schedule Management Resource
Build Filament resource for configuring report generation schedules.
Modules/FinancialReports/Filament/Admin/Resources/FinancialReportScheduleResource.phpTask 2: Create Generated Reports Management Resource
Build Filament resource for viewing and downloading generated reports.
Modules/FinancialReports/Filament/Admin/Resources/FinancialReportResource.phpStorage::download()Task 3: Add Manual Report Generation Action
Create action to generate reports on-demand from the admin interface.
🤖 Prompt for AI agents
Phase 6: Testing
Implement comprehensive PHPUnit tests covering data aggregation, report generation, scheduling logic, and edge cases.
Task 1: Unit Tests for Data Aggregation
Test financial data calculation accuracy and edge cases.
Modules/FinancialReports/Tests/Unit/Services/FinancialDataAggregatorTest.phpTask 2: Unit Tests for Period Calculation
Test date range calculations for different frequencies.
Modules/FinancialReports/Tests/Unit/Services/ReportPeriodCalculatorTest.phpCarbon::setTestNow()to control time in testsTask 3: Feature Tests for Report Generation
Test end-to-end report generation workflow.
Modules/FinancialReports/Tests/Feature/GenerateFinancialReportTest.phplast_generated_atandnext_scheduled_atupdated correctly)Task 4: Feature Tests for Scheduled Execution
Test the console command and scheduling logic.
Modules/FinancialReports/Tests/Feature/ScheduledReportGenerationTest.phpArtisan::call()to execute command in testsTask 5: Edge Case and Integration Tests
Test boundary conditions and error scenarios.
Modules/FinancialReports/Tests/Feature/FinancialReportEdgeCasesTest.php🤖 Prompt for AI agents
🚀 Next Steps
🤖 All AI agent prompts combined
💡 Iterate on the plan with:
@coderabbitai <feedback>💬 Have feedback or questions? Drop into our discord!
nielsdrost7 commentedon Jul 16, 2026
SMART Plan — Periodic Income vs. Expenses report
Specific
Let a company generate a financial summary report (total income from invoices/payments vs. total expenses) for a configurable period — annually, bi-annually, or quarterly. Current state:
Modules\Core\Enums\ReportDataSourcealready listsEXPENSEandINVOICEas report data sources, andReportFieldServiceexposes their available fields (getFieldsBySource()), but this only feeds a generic report-template designer page. No aggregation service and no scheduling/period configuration for an Income vs. Expenses summary exists yet — this needs new code, not just wiring into the designer (the designer'sReportTemplateServiceclass referenced by its Blade view doesn't currently exist in the codebase either, so it isn't a safe integration point yet).Measurable
annually,bi-annually,quarterly) in company settings.it_saves_the_configured_income_expense_report_interval()#[Test] public function it_saves_the_configured_income_expense_report_interval(): void { /* Arrange */ $this->company->update(['income_expense_report_interval' => null]); /* Act */ Livewire::actingAs($this->user) ->test(Settings::class, ['tenant' => Str::lower($this->company->search_code)]) ->fillForm(['income_expense_report_interval' => 'quarterly']) ->call('save') ->assertHasNoFormErrors(); /* Assert */ $this->assertSame('quarterly', $this->company->fresh()->income_expense_report_interval); }IncomeExpenseReportService::generate($company, $from, $to)sums invoice/payment income and expense totals within the given period, scoped to the company.it_sums_income_and_expenses_within_the_given_period()#[Test] public function it_sums_income_and_expenses_within_the_given_period(): void { /* Arrange */ Payment::factory()->for($this->company)->create(['amount' => 500, 'paid_at' => now()->subDays(5)]); Expense::factory()->for($this->company)->create(['amount' => 200, 'expensed_at' => now()->subDays(3)]); /* Act */ $report = app(IncomeExpenseReportService::class)->generate($this->company, now()->subMonth(), now()); /* Assert */ $this->assertSame(500.0, $report->income); $this->assertSame(200.0, $report->expenses); }it_excludes_transactions_outside_the_requested_period()#[Test] public function it_excludes_transactions_outside_the_requested_period(): void { /* Arrange */ Payment::factory()->for($this->company)->create(['amount' => 500, 'paid_at' => now()->subYear()]); /* Act */ $report = app(IncomeExpenseReportService::class)->generate($this->company, now()->subMonth(), now()); /* Assert */ $this->assertSame(0.0, $report->income); }it_does_not_include_another_companys_income_or_expenses()#[Test] public function it_does_not_include_another_companys_income_or_expenses(): void { /* Arrange */ $otherCompany = Company::factory()->create(); Payment::factory()->for($otherCompany)->create(['amount' => 1000, 'paid_at' => now()]); /* Act */ $report = app(IncomeExpenseReportService::class)->generate($this->company, now()->subMonth(), now()); /* Assert */ $this->assertSame(0.0, $report->income); }#[Test] public function user_can_generate_the_report_on_demand(): void { /* Arrange */ Payment::factory()->for($this->company)->create(['amount' => 500, 'paid_at' => now()]); /* Act */ $component = Livewire::actingAs($this->user) ->test(IncomeExpenseReportPage::class, ['tenant' => Str::lower($this->company->search_code)]) ->call('generate', ['from' => now()->subMonth()->toDateString(), 'to' => now()->toDateString()]); /* Assert */ $component->assertSuccessful()->assertSet('income', 500.0); }Achievable
IncomeExpenseReportServiceinModules/Core/Services/with agenerate(Company $company, Carbon $from, Carbon $to): IncomeExpenseReportDatamethod, queryingPayment(income) andExpense(expenses), both already scoped viaBelongsToCompany.income_expense_report_intervalto company settings (migration + Filament settings form field), reusing the existingSettingspage pattern.ReportTemplateServiceintegration.Modules/Core/Tests/Feature/.Relevant
Company admins need periodic visibility into profitability without manually cross-referencing invoices, payments, and expenses — this is core financial reporting, not a nice-to-have.
Time-bound
Sprint 7 (report interval/output format to be confirmed with the original reporter before implementation starts).