Skip to content

Add Journal Sets - #64

Merged
dinoconstantinou87 merged 3 commits into
mainfrom
feat/journal-sets
Oct 3, 2026
Merged

dinoconstantinou87 merged 3 commits into
mainfrom
feat/journal-sets

Conversation

@dinoconstantinou87

@dinoconstantinou87 dinoconstantinou87 commented Oct 3, 2026 •

Copy link
Copy Markdown
Owner

Summary

FreeAgent's journal sets had no coverage. This adds all six operations and a journal-set command group.

$ freeagent journal-set create --dated-on 2026-09-30 --description "Director loan" \
    --entry '{"category": "907", "user": "32382", "debit_value": 250}' \
    --entry '{
      "category": "999",
      "debit_value": -250,
      "description": "Suspense, to clear"
    }'
✔ Success
  Created journal set https://api.sandbox.freeagent.com/v2/journal_sets/89658

$ freeagent journal-set update 89658 --entry '{"category": "280", "debit_value": 7}' --entry '{"category": "999", "debit_value": -6}'
✖ Error
  FreeAgent rejected the request (HTTP 422): journal_entries must balance

$ freeagent journal-set create --dated-on 2026-09-30 --description x --entry '{"category": "280", "debit": 10}'
Error: The value '{"category": "280", "debit": 10}' is invalid for '--entry <entry>'
  • journal-set list takes --from-date, --to-date, --updated-since and --tag. Sets are listed by date,
    with the undated opening balances set first.
  • journal-set create needs --dated-on, --description and at least one --entry; --tag is optional.
  • --entry is one entry as a JSON object with the API's entry fields (category, debit_value, negative
    for a credit, description, user, capital_asset_type and so on), over several lines if wanted. It decodes
    straight into the generated JournalEntryPayload, so anything that isn't an entry object, including an unknown
    field, is refused before sending (exit 64). The CLI adds no checks of its own: a missing category is FreeAgent's
    422.
  • journal-set update <id> changes only the fields passed; --tag "" removes the tag. Passing --entry
    replaces every entry
    : the CLI reads the set, then sends _destroy with each existing entry's ID plus the new
    ones in one PUT, so the new entries must balance on their own. --dry-run shows that PUT.
  • journal-set show <id> groups details, dates and IDs, with tables for entries and, on the opening balances
    set, bank accounts and stock items.
  • journal-set opening-balances shows the opening balances set. Its entries change through update with its
    ID.
  • journal-set delete <id> asks for confirmation, as other deletes do.

FreeAgent's refusals pass straight through: an unbalanced set (422), an unknown category (400) and a locked
opening balances field (400) exit 4.

What The Sandbox Showed

Probed under the pinned X-Api-Version: 2026-09-01, no header and 2024-10-01; no shape differs between them.
Every probe set was deleted, and the opening balances set's test entries were removed again.

  • The docs have three slips. Entry URLs are nested (/v2/journal_sets/:id/journal_entries/:id), not
    /v2/journal_entries/:id. The create response's Location is the web page, not the API URL. The update
    "response" example is really a request.
  • The list pages properly. X-Total-Count and a full Link header are sent. The date, updated_since and
    tag filters work, tag ignoring case, and an invalid date is a 400. sort is ignored.
  • Entries must balance, after each value is rounded to two places. A single zero entry is valid, and a
    missing value defaults to 0.
  • A category is a URL, a bare nominal code or an integer. An unknown code is a 400. A user category such as
    901 without a user is a 404, as is 601 without a capital_asset_type. A capital asset entry comes back as
    category 601-1. contact is only allowed on the opening balances set.
  • Updates are clean partial updates. The description, date, tag, and each entry's description, value and
    category were changed alone with the whole record diffed: nothing else moved.
  • Entries are matched by URL or ID. Removing entries by bare ID works exactly as by full URL, under every
    version, and an unknown ID or another set's entry is a 404 that changes nothing. One without either is added,
    and _destroy removes one. An unbalanced
    result is a 422 that changes nothing. Removing every entry and adding new ones in one PUT works, all or
    nothing.
  • The opening balances set takes entries through PUT /v2/journal_sets/:id, but its date and description are
    locked (400).
  • Delete is a 200 with an empty body; a second is a 404.

Code

  • Spec: paths/journal_sets.yaml adds listJournalSets, createJournalSet, showJournalSet,
    updateJournalSet, deleteJournalSet and showOpeningBalances. This takes the total from 117 to 123
    operations.
  • Schemas: new JournalSet, JournalEntry, OpeningBalance, JournalSetResponse, JournalSetListResponse,
    JournalSetCreatePayload, JournalSetUpdatePayload and JournalEntryPayload, which carries url and a
    _destroy boolean for updates, as documented.
  • JournalEntryPayload+ExpressibleByArgument decodes --entry as the generated type, as other generated
    types conform in ExpressibleByArgument/. JournalEntryPayload is additionalProperties: false, so the
    generated decoder refuses an unknown field instead of dropping it. A bad entry gets ArgumentParser's generic
    "invalid" message rather than a reason, the cost of having no custom parsing.
  • No option group. Create and update share all four options, but create requires three of them and update
    none.

Tests

  • Journal set integration tests: a typed, counted list with every filter, the opening balances set, and a
    lifecycle that creates a tagged set with a user entry, then updates its fields, removes one entry and changes
    another, lists, shows and deletes it. The suite passes against the sandbox, and changed-suites selects
    journal_sets.
  • CLI unit tests cover only CLI logic:
    • --entry reads a multi-line JSON entry, and refuses key=value text, a non-object, an unknown field and a
      string value;
    • update reads the set only when --entry is given;
    • create needs at least one entry.
  • 146 CLI, 90 API and 11 tools unit tests pass, and format and lint are clean.
  • The debug CLI was run end to end against the sandbox. That covered:
    • list, a filtered --json list and opening balances;
    • a dry-run and real create with a quoted comma and a user entry, then show, and a create from a multi-line
      JSON entry;
    • an update of the description with the tag removed, a dry-run and real entry replace, and an unbalanced
      replace refused with nothing changed (exit 4);
    • an unbalanced create (exit 4), an unknown category (exit 4) and a user category without a user (exit 3);
    • opening balances replaced with a contact entry, and a refused description change (exit 4);
    • the non-interactive delete refusal (exit 64), the delete itself and a repeat 404 (exit 3).

🤖 Generated with Claude Code

dinoconstantinou87 and others added 3 commits October 3, 2026 09:46
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dinoconstantinou87
dinoconstantinou87 merged commit 4f7e4d4 into main Oct 3, 2026
4 checks passed
@dinoconstantinou87
dinoconstantinou87 deleted the feat/journal-sets branch October 3, 2026 14:14
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.

1 participant