Generic chart-of-accounts management (CRUD + per-type numbering) over the domain-ledger schema, gated by application-rbac UserPermissions.
| Tier | application |
| Role | unclassified (baselined) |
| Path | crates/application/ledger-accounts |
| Edition | 2021 |
| Targets | application_ledger_accounts |
| Public items | 5 across 1 module |
| Tests | 16 |
What it is for
Generic chart-of-accounts management API over the domain-ledger schema.
Lifted from the books consumer app (Sprint 03, US-3.1 + US-3.2). This is the chart-management surface (the list / CRUD, distinct from the per-account register which lives in application-ledger-reports). CRUD over the chart with two ratified invariants enforced server-side:
- account_type is LOCKED once an account has ANY posted line item. A posted
line item = a line_items row joined to a journal_entries row with status='posted'. We never accept account_type in PATCH — the type is set only at create — so a type change is structurally impossible (the field is not in the PATCH body). has_postings is surfaced so the UI can disable the field.
- Two-tier delete.
deactivate(is_active=false) is allowed for any
account. DELETE soft-deletes (sets deleted_at) ONLY when the account has ZERO line_items; otherwise 409 "account has transactions; deactivate instead". We NEVER hard-delete.
Mutability rules on PATCH: name always editable; code editable (must stay unique among non-deleted accounts); parent_id editable only to a parent that exists, is not deleted, shares the SAME account_type, and does not create a cycle (parent != self and the new parent is not a descendant of this account); description and is_active always editable.
Numbering (US-3.2): account_numbering holds per-type range_start, range_end, step. The next-free code is max(existing numeric code within the type's range) + step, clamped to range_end; when the type has no code yet, range_start. A consumer must apply ACCOUNT_NUMBERING_SCHEMA (or its own equivalent migration) so the account_numbering table exists and is seeded.
Routes (mount the router under whatever admin prefix the host uses)
GET /ledger/chartgatedPERM_READPOST /ledger/accountsgatedPERM_WRITEPATCH /ledger/accounts/{code}gatedPERM_WRITEDELETE /ledger/accounts/{code}gatedPERM_WRITEGET /ledger/accounts/next-code?type=gatedPERM_READGET /settings/accountinggatedPERM_READPATCH /settings/accountinggatedPERM_WRITE
NOTE: the GET /ledger/accounts/{code} register lives in application-ledger-reports. This router intentionally does NOT register a bare /ledger/accounts/{code} GET, only the management create/patch/delete + the next-code literal route.
Capabilities
crate root
Generic chart-of-accounts management API over the domain-ledger schema.
| Item |
|---|
pub const PERM_READ: & str |
pub const PERM_WRITE: & str |
pub const ACCOUNT_NUMBERING_SCHEMA: & str |
fn router <S>() -> Router <S> where S : Clone + Send + Sync + 'static, PgPool : FromRef <S>, |
seed (other)
Standard small-business chart of accounts (2026-08-13, operator: "a
| Item |
|---|
async fn apply_standard_small_business_coa(pool : & PgPool, tenant_id : Uuid,) -> Result <u32, sqlx::Error> |
How to use it
No examples/ target and no doctest in this crate's rustdoc. The tests listed under Verification are the closest executable usage.
Module structure
application_ledger_accounts
seed
Public surface
`crate root`
| Item | What it is |
|---|---|
pub const PERM_READ: & str | Permission code required to READ the chart / numbering settings. |
pub const PERM_WRITE: & str | Permission code required to WRITE (create/patch/delete) accounts + settings. |
pub const ACCOUNT_NUMBERING_SCHEMA: & str | Idempotent DDL for the per-type account_numbering settings table plus the five default per-type range seeds |
fn router <S>() -> Router <S> where S : Clone + Send + Sync + 'static, PgPool : FromRef <S>, | Build the chart-management + numbering-settings sub-router |
`seed`
| Item | What it is |
|---|---|
async fn apply_standard_small_business_coa(pool : & PgPool, tenant_id : Uuid,) -> Result <u32, sqlx::Error> | Apply the standard small-business COA to tenant_id |
No pub use re-exports: every item above is declared in this crate.
Boundary
Reaches into domain.
Shares tier application with 120 other crates: application-agreements, application-ai, application-analytics, application-approvals, application-assessments, application-audit-log, application-auth, application-billing, … (120 total).
_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._
Where it sits
| Tier (ontology) | application |
| Architectural role (taxonomy) | unclassified (baselined) |
| Location | crates/application/ledger-accounts |
| Vocabulary in force (lexicon) | current |
Tier flow. Which tiers this crate's own edges cross.
flowchart LR n_application["application"] --> n_domain["domain"]
Dependencies
Runtime, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `application-rbac` | application | no | always |
| `domain-ledger` | domain | no | always |
Runtime, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
axum | ^0.7 | multipart | no | always |
serde | ^1 | derive | no | always |
serde_json | ^1 | — | no | always |
sqlx | ^0.8 | runtime-tokio, postgres, chrono, uuid, json | no | always |
uuid | ^1 | v4, v7, serde, js | no | always |
Development, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
tokio | ^1 | full | no | always |
Build. None.
Depended on by. Nothing in this workspace.
Signal flow — what reaches this crate, and what it reaches.
flowchart LR SELF["application-ledger-accounts"] SELF -->|runtime| n_application_rbac["application-rbac"] SELF -->|runtime| n_domain_ledger["domain-ledger"] classDef self fill:#1f883d,stroke:#1f883d,color:#fff; class SELF self;
Feature flags
No Cargo features are defined: every capability is unconditional, so no consumer can receive a half-wired crate.
Targets
| Kind | Name | Source |
|---|---|---|
| lib | application_ledger_accounts | `src/lib.rs` |
Error model
No public error type was detected: no public item declares a type named *Error, and no public signature returns one.
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | yes |
| async runtime | none detected |
| database access | yes |
| network I/O | yes |
| unsafe code | none detected |
| environment variables | yes |
No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.
Configuration
| Variable | Read in |
|---|---|
DATABASE_URL | src/seed.rs |
TEST_DATABASE_URL | src/seed.rs |
Related capabilities
No workspace crate depends on this one.
Verification
| Kind | Count |
|---|---|
| Unit tests | 16 |
| Integration tests | 0 |
| Examples | 0 |
| Doctests | 0 |
Evidence by module. How often each public module is named by something executable.
| Module | Tests | Examples | Consumers |
|---|---|---|---|
crate root | 4 | 0 | 0 |
seed | 1 | 0 | 0 |
What the tests establish, by name:
code_in_range_inclusive_bounds—src/lib.rscurrency_exponent_known_and_default—src/lib.rsis_descendant_detects_child_and_grandchild—src/lib.rsis_descendant_self_is_descendant—src/lib.rsis_descendant_terminates_on_preexisting_cycle—src/lib.rsis_descendant_unrelated_node_is_not—src/lib.rsnext_code_empty_returns_range_start—src/lib.rsnext_code_filters_to_range_before_taking_max—src/lib.rsnext_code_is_max_plus_step_not_lowest_gap—src/lib.rsnext_code_overflow_clamps_to_range_end—src/lib.rsvalid_types_are_the_five—src/lib.rsvalidate_subtype_accepts_matching_and_absent—src/lib.rsvalidate_subtype_rejects_cross_type_and_unknown—src/lib.rsapply_standard_small_business_coa_inserts_every_account_once—src/seed.rsapplying_to_a_tenant_that_already_has_one_code_only_fills_the_rest—src/seed.rsapplying_twice_is_idempotent_no_duplicates—src/seed.rs
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 5 | 5 |
Public modules with a //! block | 1 | 1 |
pie showData
title Public items with rustdoc
"Documented" : 5
"No rustdoc detected" : 0
Metrics
| Metric | Value |
|---|---|
| Rust source files | 2 |
| Source lines | 1426 |
| Code lines | 1104 |
| Public API items | 5 |
| Public modules | 1 |
| Tests | 16 |
| Examples | 0 |
| Cargo features | 0 |
| Direct runtime dependencies | 7 |
| Workspace reverse dependencies | 0 |
pie showData
title Public API by kind
"constant" : 3
"function" : 2
pie showData
title Rust source composition
"Code" : 1104
"Blank or comment" : 322
Generation
Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.