Skip to content

Add the invoice card to the Desk - #757

Merged
emoss08 merged 4 commits into
masterfrom
claude/vibrant-galileo-cndn0k
Oct 14, 2026
Merged

emoss08 merged 4 commits into
masterfrom
claude/vibrant-galileo-cndn0k

Conversation

@emoss08

@emoss08 emoss08 commented Oct 14, 2026

Copy link
Copy Markdown
Owner

Description

Adds an invoice resource card to the Desk: an overview of an invoice with a few actions, opened beside the conversation that looked it up. The billing queue stays the place for full detail.

The card covers these states:

  • posted
  • draft, waiting to post
  • fresh, with nothing owed yet
  • credit memo, showing the invoice it corrects and where it was applied
  • voided, showing who voided it and why
  • empty and record gone

It has these sections:

  • What is owed, with a terms ruler (running, late or unposted)
  • Charges
  • Payments and credits
  • Dispute
  • Split with or Related invoices
  • Where it went (email, EDI 210 and accounting system slips)
  • Shipments it bills (origin and destination only)
  • Activity

Invoice lines now store the ID of the formula template they were rated with, so the template name on a freight line opens the formula template card. The name is truncated, with the full name on hover. Long text on the email and EDI slips, such as why a 210 wasn't sent, truncates the same way.

Related Issue or Discussion

The maintainer requested and approved this from mockups in a Claude Code session.

Type of Change

  • Bug fix
  • Feature
  • Documentation
  • Refactor
  • Tests
  • Build, CI, or infrastructure

Scope

Server

  • billing/domain/invoice:
    • adds InvoiceLine.FormulaTemplateID;
    • ChargeGroups groups lines into the card's charge rows;
    • BilledShipmentIDs lists the shipments an invoice bills.
  • billing/app/invoicelines: the freight line records the template it was rated with. It prefers the rating detail and falls back to the assigned template only when the names match.
  • Migration 20261231009470_invoice_line_formula_template: adds the column and backfills existing freight lines only where the template name matches. Lines it can't match stay null.
  • GraphQL:
    • adds Invoice.chargeSummary, Invoice.voidedBy, InvoiceLine.formulaTemplateId, InvoiceDispute.openedBy/resolvedBy and ShipmentsInput.invoiceIds;
    • updates projection.yml and gqlgen.yml;
    • regenerates the executor.
  • invoiceIds requires invoice read permission. The new ShipmentOptions.IDs filter is how it reaches the repository.

Client

  • routes/desk/_components/artifacts/cards/invoice/: the card, its parts, skeleton, helpers and tests. Its styles are the dk-inv-* block in desk-v2.css.
  • lib/graphql/invoice-card.ts, the DeskInvoiceCard operation, and lib/queries/invoice.ts (card, lastEmailAttempt).
  • hooks/use-clipped.ts: tells the card when text is truncated, so the tooltip only shows then.
  • lib/accounting-sync.ts: the sync state labels, now shared with sync-state-line.tsx.
  • Registry and kind marks.
  • Mock API fixtures and screenshot groups invoice and invoice-closed.

Generated and docs

  • i18n: 90 new strings in es, zh-CN and zh-TW.
  • Product guide catalog.
  • docs/engineering/desk-artifacts.md.

Validation

  • cd services/tms && task test: go test passes for the changed packages. The new backfill integration test was run with -tags integration against a local Postgres 16, and it fails when the backfill picks the wrong template.
  • cd services/tms && task lint: golangci-lint (--new-from-rev=origin/master) reports 0 new issues, and go run ./scripts/archlint passes.
  • cd client && pnpm typecheck: tsc -b passes in every package (run per package because turbo fails in the container).
  • cd client && pnpm build: not run locally; CI covers the build and bundle budget.
  • cd client && pnpm lint: oxlint, lint:design and lint:skeletons pass.
  • Other:
    • Web tests: 2,649 passed before the master merge; after it, 1,206 passed across the Desk, GraphQL and activity-timeline suites.
    • task gqlschema-diff: 8 safe changes, 0 breaking.
    • The generators produce no further changes.
    • node i18n/tools/i18n.mjs check passes, and so does pnpm --filter @trenova/web guide:check.
    • 28 mock screenshots were taken with no console errors.

Screenshots or Recordings

Mock screenshots of every state (loaded, attention, empty, skeleton, error, full page and narrow, in light and dark) were reviewed and approved in the session. To regenerate them, run node e2e/mock-api/desk-cards-screenshots.mjs with the invoice and invoice-closed groups.

Deployment Notes

  • One migration adds the nullable invoice_lines.formula_template_id column and backfills it from the shipments' rating detail and assigned template. Lines without a matching template name stay null.
  • No config or env changes.
  • The merge from master also adds Spanish and Chinese translations for the two shipment activity-feed strings master shipped without them. Without them, the i18n check fails on master too.
  • Known limit: the accounting system slip only appears once an invoice has a sync record. Before the first sync, the card can't cheaply name which integration is connected.

Checklist

  • I kept the change focused and reviewable.
  • I followed AGENTS.md, CLAUDE.md, and existing repository patterns.
  • I added or updated tests for behavior changes, or explained why tests are not applicable.
  • I updated relevant documentation, examples, migrations, or configuration.
  • I did not include secrets, credentials, private customer data, unrelated refactors, or placeholder code.

🤖 Generated with Claude Code

https://claude.ai/code/session_012Bt5FWcHvsb67pNyemFnr2


Generated by Claude Code

A freight line kept only the template's name, so nothing could link back
to the template itself. It now stores the id beside the name: the template
the shipment's rating names, else, for a rating that only kept a name, the
template the shipment is assigned when that template still has that name.
An id that is not a PULID is left out rather than stored. Copying a line's
charge detail carries the id.

The migration adds the column and fills it on existing freight lines from
the shipment's rating or assigned template, only where the template still
exists and its name matches what the line printed; anything else is left
without an id rather than guessed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Bt5FWcHvsb67pNyemFnr2
An invoice looked up by the agent, or opened from a table's row or another
card, now draws as a card of its own rather than a list of fields. It reads
what a biller and a collector read it for: who it bills and what is still
owed; the terms as a ruler from the invoice date to the due date, past it in
amber once late, or, for a draft, how many of the customer's days have run
while it waits to be posted; the charges summed by how each was billed, the
freight named by the formula template that rated it and opening that
template's card; payments and credits as remittance stubs; the open dispute
over the closed ones; where it went by email, EDI 210 and to whichever
accounting system the organization keeps, each line cut to one row and told
in full on hover only when cut; and the shipments it bills, origin to
destination. A voided invoice says why and what became of its freight; a
credit memo, what it corrects, what it has left and the invoices it settled.

The server adds what the card needs and nothing the page did not already
load: Invoice.chargeSummary sums the lines GetByID already reads into one
row per way a charge was billed, so a consolidated invoice of hundreds of
lines is a handful of rows; the users who voided an invoice and opened and
resolved its disputes; the formula template on each invoice line; and
ShipmentsInput.invoiceIds, which needs permission to read invoices and is
resolved in the API layer so the operations module never reads a billing
table. Every schema change is additive.

The accounting sync state's labels move into lib/accounting-sync so the
invoice page's sync line and the card name the system the same way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Bt5FWcHvsb67pNyemFnr2
Regenerates the translation catalogs and product guide catalog over both
sides, and translates the two activity-feed strings master left untranslated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Bt5FWcHvsb67pNyemFnr2
@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 13 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 11eab79c-ebeb-497d-be93-8c8485931bb8
📥 Commits

Reviewing files that changed from the base of the PR and between 595734d and a1ecede.

⛔ Files ignored due to path filters: 11
  • client/packages/graphql/src/generated/documents/invoice/desk.ts is excluded by !**/generated/**
  • client/packages/graphql/src/generated/gql.ts is excluded by !**/generated/**
  • client/packages/graphql/src/generated/graphql.ts is excluded by !**/generated/**
  • client/packages/graphql/src/generated/persisted-documents.json is excluded by !**/generated/**
  • services/tms/internal/api/graphql/generated/exec/invoicedisputeexec/invoicedispute.generated.go is excluded by !**/*.generated.*, !**/generated/**
  • services/tms/internal/api/graphql/generated/exec/invoiceexec/invoice.generated.go is excluded by !**/*.generated.*, !**/generated/**
  • services/tms/internal/api/graphql/generated/exec/shipmentexec/shipment.generated.go is excluded by !**/*.generated.*, !**/generated/**
  • services/tms/internal/api/graphql/generated/root.generated.go is excluded by !**/*.generated.*, !**/generated/**
  • services/tms/internal/api/graphql/resolver/invoiceresolver/resolvers.generated.go is excluded by !**/*.generated.*
  • services/tms/internal/api/graphql/resolver/resolver.generated.go is excluded by !**/*.generated.*
  • services/tms/internal/api/graphql/resolver/shipmentresolver/resolvers.generated.go is excluded by !**/*.generated.*
📒 Files selected for processing: 77
  • client/apps/web/e2e/mock-api/desk-cards-screenshots.mjs
  • client/apps/web/e2e/mock-api/fixtures/desk-cards.mjs
  • client/apps/web/e2e/mock-api/fixtures/desk-invoices.mjs
  • client/apps/web/e2e/mock-api/handlers/deskcards.mjs
  • client/apps/web/e2e/mock-api/handlers/palette.mjs
  • client/apps/web/src/components/accounting-sync/sync-state-line.tsx
  • client/apps/web/src/hooks/use-clipped.ts
  • client/apps/web/src/lib/accounting-sync.ts
  • client/apps/web/src/lib/graphql/__tests__/record-shipments.test.ts
  • client/apps/web/src/lib/graphql/invoice-card.ts
  • client/apps/web/src/lib/graphql/record-shipments.ts
  • client/apps/web/src/lib/queries/invoice.ts
  • client/apps/web/src/routes/desk/_components/artifacts/cards/invoice/__tests__/desk-invoice.test.tsx
  • client/apps/web/src/routes/desk/_components/artifacts/cards/invoice/__tests__/fixtures.ts
  • client/apps/web/src/routes/desk/_components/artifacts/cards/invoice/__tests__/invoice-card.test.ts
  • client/apps/web/src/routes/desk/_components/artifacts/cards/invoice/desk-invoice.tsx
  • client/apps/web/src/routes/desk/_components/artifacts/cards/invoice/invoice-card.ts
  • client/apps/web/src/routes/desk/_components/artifacts/cards/invoice/invoice-parts.tsx
  • client/apps/web/src/routes/desk/_components/artifacts/cards/invoice/invoice-sections.ts
  • client/apps/web/src/routes/desk/_components/artifacts/cards/invoice/invoice-skeleton.tsx
  • client/apps/web/src/routes/desk/_components/artifacts/cards/registry.tsx
  • client/apps/web/src/routes/desk/_components/artifacts/desk-art-kinds.tsx
  • client/apps/web/src/routes/desk/_styles/desk-v2.css
  • client/packages/graphql/src/operations/invoice/desk.graphql
  • client/packages/graphql/src/schema.graphql
  • client/packages/shared/src/i18n/catalogs/es/routes.carrier-invoice-matching.json
  • client/packages/shared/src/i18n/catalogs/es/routes.desk.json
  • client/packages/shared/src/i18n/catalogs/es/routes.invoice.json
  • client/packages/shared/src/i18n/catalogs/es/routes.reports.json
  • client/packages/shared/src/i18n/catalogs/es/routes.shipment.json
  • client/packages/shared/src/i18n/catalogs/es/web.json
  • client/packages/shared/src/i18n/catalogs/zh-CN/routes.carrier-invoice-matching.json
  • client/packages/shared/src/i18n/catalogs/zh-CN/routes.desk.json
  • client/packages/shared/src/i18n/catalogs/zh-CN/routes.invoice.json
  • client/packages/shared/src/i18n/catalogs/zh-CN/routes.reports.json
  • client/packages/shared/src/i18n/catalogs/zh-CN/routes.shipment.json
  • client/packages/shared/src/i18n/catalogs/zh-CN/web.json
  • client/packages/shared/src/i18n/catalogs/zh-TW/routes.carrier-invoice-matching.json
  • client/packages/shared/src/i18n/catalogs/zh-TW/routes.desk.json
  • client/packages/shared/src/i18n/catalogs/zh-TW/routes.invoice.json
  • client/packages/shared/src/i18n/catalogs/zh-TW/routes.reports.json
  • client/packages/shared/src/i18n/catalogs/zh-TW/routes.shipment.json
  • client/packages/shared/src/i18n/catalogs/zh-TW/web.json
  • docs/engineering/desk-artifacts.md
  • i18n/messages.en.json
  • i18n/messages.es.json
  • i18n/messages.zh-CN.json
  • i18n/messages.zh-TW.json
  • services/tms/gqlgen.yml
  • services/tms/internal/api/graphql/gqlmodel/models_gen.go
  • services/tms/internal/api/graphql/persisted-documents.json
  • services/tms/internal/api/graphql/projection/projection.yml
  • services/tms/internal/api/graphql/projection/specs_gen.go
  • services/tms/internal/api/graphql/resolver/invoicedisputeresolver/invoice_dispute.resolvers.go
  • services/tms/internal/api/graphql/resolver/invoiceresolver/charge_summary_test.go
  • services/tms/internal/api/graphql/resolver/invoiceresolver/invoice.resolvers.go
  • services/tms/internal/api/graphql/resolver/shipmentresolver/shipment.resolvers.go
  • services/tms/internal/api/graphql/resolver/shipmentresolver/shipment_invoice_filter.go
  • services/tms/internal/api/graphql/resolver/shipmentresolver/shipments_assignment_filters_test.go
  • services/tms/internal/api/graphql/schema/invoice.graphqls
  • services/tms/internal/api/graphql/schema/invoice_dispute.graphqls
  • services/tms/internal/api/graphql/schema/shipment.graphqls
  • services/tms/internal/infrastructure/postgres/migrations/20261231009470_invoice_line_formula_template.tx.down.sql
  • services/tms/internal/infrastructure/postgres/migrations/20261231009470_invoice_line_formula_template.tx.up.sql
  • services/tms/internal/modules/billing/adapters/postgres/invoicerepository/formula_template_backfill_integration_test.go
  • services/tms/internal/modules/billing/app/invoicelines/invoicelines.go
  • services/tms/internal/modules/billing/app/invoicelines/invoicelines_test.go
  • services/tms/internal/modules/billing/domain/invoice/charge_group.go
  • services/tms/internal/modules/billing/domain/invoice/charge_group_test.go
  • services/tms/internal/modules/billing/domain/invoice/invoice.go
  • services/tms/internal/modules/billing/domain/invoice/line_test.go
  • services/tms/internal/modules/operations/adapters/postgres/shipmentrepository/activity_window_test.go
  • services/tms/internal/modules/operations/adapters/postgres/shipmentrepository/helpers.go
  • services/tms/internal/modules/operations/ports/shipmentports/shipment.go
  • services/tms/pkg/buncolgen/invoicecol/invoice_gen.go
  • services/tms/pkg/productguide/catalog_gen.json
  • services/tms/pkg/reportcatalog/catalog_gen.go
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@emoss08
emoss08 merged commit a061837 into master Oct 14, 2026
11 checks passed
@emoss08
emoss08 deleted the claude/vibrant-galileo-cndn0k branch October 14, 2026 23:55
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.

2 participants