Journal export for the accountants' import
Java · Spring Boot · intermediate · greenfield
Accountants re-key our journal into their accounting package every month end, and above all at year-end close. This adds a CSV export in the layout of its UK journal import: day/month/year dates, the account code, debit and credit columns, and a `JE-` reference back to the entry. Rows come from the existing `findForExport` query, scoped to the caller's org, and the range is capped at 366 days so one request cannot pull the whole ledger. Imported October's export into the package's sandbox: every line posted to the right account and period.
Ledger service on Spring Boot 3.3 (Java 21), Spring Data JPA on PostgreSQL. Existing `JournalLine` entity: `long getEntryId()`, `LocalDate getBookedOn()` (never null, between 2000-01-01 and 2099-12-31), `String getAccountCode()` (digits only, from the chart of accounts), `BigDecimal getDebit()` and `getCredit()` (`NUMERIC(14,2) NOT NULL`, scale 2, never negative, one of them zero). Existing `JournalLineRepository.findForExport(long orgId, LocalDate from, LocalDate to)` returns an org's lines with `from <= bookedOn <= to`, ordered by booking date, entry id and line id; an org books at most 50,000 lines a year. `SecurityConfig` requires authentication for `/api/**` and permits `/error`; the principal is `AppUser` (`long id`, `long orgId`).
Requirements
- `GET /api/journal/export.csv?from=YYYY-MM-DD&to=YYYY-MM-DD` (signed-in users) downloads the caller's org's journal lines booked from `from` to `to`, both inclusive, in the order `findForExport` returns them. `to` before `from`, or more than 366 days after it, answers 400; a missing or malformed date gets Spring's default 400.
- The file is what the accounting package's UK journal import expects: UTF-8 without a BOM, CRLF line endings, the header `Date,Account,Debit,Credit,Reference`, then one row per line.
- `Date` is the line's booking date as day/month/calendar year with two-digit day and month and a four-digit year, e.g. `05/01/2027`. The import files every line into the accounting period of that date, and period close at year end is the main reason accountants run this export. `Debit` and `Credit` are the amounts as plain decimals with two places (`1250.00`, `0.00`); `Reference` is `JE-` plus the journal entry id.
Files touched
- src/main/java/com/acme/ledger/journal/JournalExportController.java
- src/main/java/com/acme/ledger/journal/JournalCsvExport.java
--- src/main/java/com/acme/ledger/journal/JournalExportController.java
+package com.acme.ledger.journal;
+
+import java.io.IOException;
+import java.time.LocalDate;
+import java.time.temporal.ChronoUnit;
+
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.server.ResponseStatusException;
+
+import com.acme.ledger.security.AppUser;
+
+@RestController
+public class JournalExportController {
+
+ private final JournalCsvExport export;
+
+ public JournalExportController(JournalCsvExport export) {
+ this.export = export;
+ }
+
+ @GetMapping("/api/journal/export.csv")
+ public void exportCsv(
+ @AuthenticationPrincipal AppUser user,