Skip to main content

Backend

The Taxes module manages tax payment records with a global index (filters + statistics), CRUD operations, document attachment management, and Google Calendar integration for due dates.

File Structure

app/
├── Http/
│ ├── Controllers/Taxes/
│ │ ├── TaxController.php
│ │ └── TaxAttachmentController.php
│ └── Requests/Taxes/
│ ├── StoreTaxRequest.php
│ ├── UpdateTaxRequest.php
│ └── UploadTaxAttachmentRequest.php
├── Models/
│ └── Tax.php
├── Queries/Taxes/
│ ├── TaxIndexQuery.php
│ └── TaxStatsQuery.php
└── Services/Taxes/
├── TaxAttachmentService.php
└── Storage/
└── TaxAttachmentStorageManager.php

Routes

Tax CRUD

MethodURINameDescription
GET/taxestaxes.indexGlobal paginated tax list with filters
POST/taxestaxes.storeCreate tax record
PUT/taxes/{tax}taxes.updateUpdate tax record
DELETE/taxes/{tax}taxes.destroyDelete tax record

Attachment Routes

MethodURINameDescription
POST/taxes/{tax}/attachmenttaxes.attachment.uploadUpload document
GET/taxes/{tax}/attachment/downloadtaxes.attachment.downloadDownload document
GET/taxes/{tax}/attachment/previewtaxes.attachment.previewPreview document
DELETE/taxes/{tax}/attachmenttaxes.attachment.destroyDelete document

Controllers

TaxController

Uses Query Classes pattern to separate query/filter logic from the controller.

Methods

  • index() — uses TaxIndexQuery for paginated list, TaxStatsQuery for stat cards, getAvailableYears() for the year select
  • store() — validates with StoreTaxRequest, creates the record and redirects to taxes.index
  • update() — validates with UpdateTaxRequest, updates the record and redirects to taxes.index
  • destroy() — deletes the record (model boot() handles attachment cleanup) and redirects to taxes.index

getAvailableYears()

Private helper that returns range(now()->year, 2026). Starts from 2026 (application birth year) and grows automatically each year. Logic lives in the controller, not in the view.

TaxAttachmentController

Delegates all file operations to TaxAttachmentService.

Methods

  • upload() — validates with UploadTaxAttachmentRequest, delegates to TaxAttachmentService::upload()
  • download() — returns a StreamedResponse via TaxAttachmentService::download()
  • preview() — returns a BinaryFileResponse via TaxAttachmentService::preview()
  • destroy() — deletes the file and nullifies tax.attachment via TaxAttachmentService::delete()

Model

The Tax model is located in app/Models/Tax.php and implements CalendarEventable.

Fields

FieldTypeDescription
descriptionstringTax description
amountdecimal(10,2)Tax amount
due_datedatePayment deadline
paid_atdate|nullActual payment date (null = unpaid)
reference_yearsmallIntegerFiscal year the tax refers to
attachmentstring|nullLocal file path of the document
notestext|nullOptional notes

Scopes

ScopeDescription
referenceYear($year)Filter by reference year
paid()Records with paid_at not null
unpaid()Records with paid_at null

Helpers

  • hasAttachment() — returns true if a document is attached
  • getAttachmentUploadUrl() — upload route URL
  • getAttachmentDownloadUrl() — download route URL (null if no attachment)
  • getAttachmentPreviewUrl() — preview route URL (null if no attachment)
  • getAttachmentDeleteUrl() — delete route URL (null if no attachment)
  • toFormPayload() — safe edit payload (id + all fillable fields)
  • Delete hookboot() deletes the local file when the record is deleted

CalendarEventable

Tax implements the CalendarEventable contract:

  • hasCalendarDate() — returns true if due_date is set
  • toCalendarEvent() — returns a CalendarEvent DTO (all-day event on due date, title includes date/description/year, body includes all tax details and notes)
  • googleCalendarUrl() — builds the Google Calendar link via GoogleCalendarLinkBuilder::fromModel($this)->build()

Form Requests

StoreTaxRequest / UpdateTaxRequest

Required Fields

  • description — string, max 255
  • amount — numeric, min 0.01, max 999999.99
  • due_date — date
  • reference_year — integer, min 2000, max 2100

Optional Fields

  • paid_at — nullable date
  • notes — nullable string

UploadTaxAttachmentRequest

  • attachment — required, file, mimes: pdf,jpg,jpeg,png, max 10 MB

Query Classes

TaxIndexQuery

Manages the global tax list with:

  • pagination (15)
  • filters: reference_year, paid (1 = paid, 0 = unpaid)
  • full-text search on description
  • sorting by due_date desc

TaxStatsQuery

Calculates statistics for the four index stat cards (no currency filter — taxes have no currency field):

KeyDescription
total_all_timeSum of all paid taxes
total_this_yearSum of taxes paid in the current year
unpaid_amountSum of all unpaid taxes
count_this_yearCount of taxes with due_date in the current year

Services

TaxAttachmentService

Orchestrates file operations:

  • upload() — deletes existing attachment if present, generates a locale-aware filename, saves via TaxAttachmentStorageManager, updates tax.attachment
  • download() / preview() — delegates to storage manager after checking file existence
  • delete() — deletes file and nullifies tax.attachment

Filename generation

"{$prefix}-{$year}-{$date}-" . time() . ".{$extension}"
// e.g. tax-document-2026-2026-03-11-1741698000.pdf

$prefix uses Str::slug(__('taxes.attachment')) so it is locale-aware and never hardcoded.

TaxAttachmentStorageManager

Handles low-level Storage disk operations (save, delete, exists, download, preview).