Files
integreat/docs/testing/behaviors/outgoing-invoice.md
Bryce b499d460f3 docs: add comprehensive test behavior documentation for all pages
Add behavior documentation covering all SSR and legacy SPA pages:
- Testing strategy and type definitions (unit/integration/UI)
- Dashboard, Invoice, Payment, Transaction, Ledger pages
- Company/Settings, POS, Admin, Search, Auth pages
- Legacy SPA behavior docs (no UI tests until migrated)
- Edge cases, test data requirements, and dependencies per subsystem

Total: 3,600+ lines of behavior documentation to guide test authorship.
2026-05-04 12:15:20 -07:00

8.6 KiB

Outgoing Invoice Behaviors

Overview

The Outgoing Invoice subsystem allows users to create and generate PDF invoices to send to customers. It provides a form-based workflow for specifying the client (sender), recipient details, invoice metadata (date, number, tax rate), and line items. Upon submission, the system calculates subtotals, applies tax, and invokes an AWS Lambda function (genpdf) to generate a downloadable PDF.

Routes & Pages

Route Method Handler Purpose
GET /outgoing-invoice/new GET ::new Renders the new outgoing invoice form
POST /outgoing-invoice/new POST ::new-submit Validates form, generates PDF, shows download modal
GET /outgoing-invoice/line-item/new GET ::new-line-item Returns HTML for a new empty line item row (HTMX)

Behaviors

Unit Tests

  • form-schema validates required fields: client, date, to, invoice-number, tax, to-address, line-items
  • form-schema requires line-item description, unit-price, and quantity
  • form-schema makes address street2 optional (nullable string)
  • form-schema coerces line-items vector from form params
  • form-schema applies strip decoder to street2 (trims whitespace, treats empty as nil)
  • line-item renders a data-grid row with hidden db/id, description input, quantity money-input, unit-price money-input, and delete button
  • line-item delete button uses Alpine.js to fade out and remove row after 500ms
  • submit filters out line items with empty descriptions
  • submit calculates line-item total as unit-price * quantity
  • submit calculates subtotal as sum of all line-item totals
  • submit calculates tax as subtotal * tax-rate (tax is a percentage, e.g., 10.0 = 10%)
  • submit calculates total as subtotal + tax
  • submit formats monetary values as $X,XXX.XX strings before sending to Lambda
  • submit formats invoice date as normal-date string before sending to Lambda
  • submit invokes genpdf Lambda with JSON payload
  • submit extracts S3 URL from Lambda response and presents download link
  • fmt-money formats nil as $0.00
  • fmt-money formats large numbers with comma separators (e.g., $1,234.56)

Integration Tests

  • GET /outgoing-invoice/new returns 200 with full form HTML for authenticated user
  • Form includes client typeahead, invoice number input, date picker, line items grid, tax input, and recipient address fields
  • Client typeahead fetches from :company-search endpoint
  • Date input renders with normal-date formatted value
  • Form POSTs to ::new-submit via HTMX
  • POST /outgoing-invoice/new with valid data returns 200 with modal containing PDF download link
  • POST /outgoing-invoice/new with invalid data returns form with validation errors
  • Missing required fields (client, date, to, invoice-number) show validation errors
  • Empty line items (no description) are filtered out before calculation
  • GET /outgoing-invoice/line-item/new returns HTML for a new line item row
  • New line item row can be appended to the line items grid via HTMX
  • Multiple line items can be added and each calculates independently
  • Form is wrapped with wrap-schema-decode using form-schema
  • Form is wrapped with wrap-nested-form-params to handle nested form parameter keys
  • Unauthenticated requests redirect to /login
  • Request applies wrap-trim-client-ids and wrap-secure middleware

UI Test Behaviors (SSR)

Happy Path: Create and Generate Invoice

  1. Authenticated user navigates to outgoing invoice new page
  2. Page renders with breadcrumbs: Invoices > Outgoing > New
  3. User selects a client from the typeahead ("From (client)")
  4. User enters invoice number (e.g., "10000")
  5. User selects/enters invoice date
  6. User enters recipient name in "To" field
  7. User fills in recipient address: street1, city, state, zip (street2 optional)
  8. User adds line items: clicks "Add line", enters description, quantity, unit price
  9. User adds multiple line items
  10. User verifies or adjusts tax percentage (default 10.0)
  11. User clicks "Generate" button
  12. Server validates form, calculates totals, invokes PDF Lambda
  13. Modal appears with message "Download your invoice" and link to S3 URL
  14. User clicks link to download PDF

Add and Remove Line Items

  1. User views new invoice form with one default empty line item
  2. User clicks "Add line" button
  3. HTMX fetches new line item row and appends it to the grid
  4. User fills in the new line item
  5. User clicks delete (X) button on a line item
  6. Row fades out and removes itself via Alpine.js
  7. Remaining line items retain their data

Validation Errors

  1. User clicks "Generate" without filling required fields
  2. Form POSTs and returns with validation error styling
  3. Required fields show error indicators
  4. User fills in missing fields and resubmits
  5. Invoice generates successfully

Empty Line Items Handling

  1. User adds multiple line items
  2. User leaves some line item descriptions blank
  3. User submits form
  4. Server filters out empty line items
  5. PDF generates with only populated line items
  6. Subtotal and total reflect only populated items

Edge Cases

Form Validation

  • Missing client: Typeahead shows error state; form cannot submit
  • Missing date: Date input shows error state
  • Missing recipient name: "To" field shows error state
  • Missing invoice number: Invoice # field shows error state
  • Invalid date format: Schema rejects; form redisplays with error
  • Negative quantities: Money input may accept negatives; calculation handles them
  • Zero unit price: Line item total is $0.00; included in subtotal
  • Zero tax rate: Tax is $0.00; total equals subtotal
  • Very large numbers: Monetary formatting uses commas; Lambda payload handles as string

Line Items

  • All line items empty: Subtotal is 0.0, tax is 0.0, total is 0.0
  • Single line item: Calculates correctly
  • Many line items (50+): Grid scrolls; each item calculates independently
  • Delete all line items: Grid empty; adding new row restores functionality
  • Line item with only description: Filtered out (requires description + values)

PDF Generation

  • Lambda invocation failure: Exception propagates; user sees error (no modal)
  • Lambda returns invalid JSON: json/read-str may throw; error handling depends on catch logic
  • S3 URL inaccessible: Link is presented but may 403/404 on click
  • Very large invoice payload: Lambda payload size limits may apply

Permissions & Auth

  • Unauthenticated user: Redirected to /login?redirect-to=/outgoing-invoice/new
  • Session expired during form fill: HTMX POST returns hx-redirect to login
  • User without client access: Client typeahead only shows accessible clients

Tax Calculation

  • Tax as whole number (10): Treated as 10% (multiplied by 0.10)
  • Tax with decimals (8.25): Treated as 8.25%
  • Tax over 100%: Allowed by schema; mathematically valid but business-questionable
  • Zero tax: Total equals subtotal exactly

Test Data Requirements

Users

  • Authenticated user with access to at least one client
  • Client with complete profile including address

Clients

  • Client with :client/name and :client/address (street, city, state, zip)
  • Multiple clients to test typeahead selection

Form Data

  • Valid invoice number strings
  • Valid dates in normal-date format
  • Recipient names and addresses
  • Line items with descriptions, quantities (numeric), unit prices (monetary)
  • Tax rates (percentage values, e.g., 10.0 for 10%)

AWS Lambda Mock

  • Mock genpdf Lambda invocation returning valid S3 URL
  • Mock genpdf Lambda returning error

Dependencies

External Services

  • AWS Lambda: genpdf function generates PDF from invoice data
  • S3: Generated PDFs stored at data.prod.app.integreatconsult.com/<path>
  • Datomic: Client lookup for typeahead, address data

Frontend Libraries

  • HTMX: Form submission, line item row fetching
  • Alpine.js: Line item row show/hide and removal animation
  • Form cursor (auto-ap.ssr.form-cursor): Field state management, error binding

Middleware Stack

  • wrap-secure: Requires authentication
  • wrap-client-redirect-unauthenticated: Redirects unauthenticated users
  • wrap-trim-client-ids: Trims client IDs from request
  • wrap-schema-decode: Validates and decodes form data against form-schema
  • wrap-nested-form-params: Parses nested form parameter structures
  • Invoices: Outgoing invoices are linked from the main invoices page
  • Company/Clients: Client typeahead depends on company search endpoint
  • Company Search: Typeahead fetches from :company-search route