Skip to content

feat(invoices): manual invoice creation, work-order conversion, configurable From identities - #49

Merged
Dev869 merged 1 commit into
mainfrom
feat/invoices-and-from-identities
May 19, 2026
Merged

Dev869 merged 1 commit into
mainfrom
feat/invoices-and-from-identities

Conversation

@Dev869

@Dev869 Dev869 commented May 19, 2026

Copy link
Copy Markdown
Owner

Summary

Makes "is this an invoice" explicit and adds the missing pieces around manual invoicing, work-order conversion, and configurable sender identities.

1. Predicate: explicit invoice classification (backward compatible)

  • New WorkItem.invoicedAt?: Date — set when an item becomes an invoice via manual creation or conversion, independent of being emailed.
  • isInvoice = Boolean(invoicedAt) || Boolean(invoiceSentDate) || invoiceStatus ∈ {sent,paid,overdue}. isWorkOrder = !isInvoice unchanged.
  • Backward compatible: legacy invoices that were only ever "sent" still classify correctly via the existing sent-date / status checks. No data migration.
  • This also fixes a latent bug: manually-created draft invoices (invoiceStatus: 'draft', no send) previously leaked into work-order views because draft is not an invoice status. They now classify via invoicedAt.
  • Firestore converter reads/writes invoicedAt (Timestamp ↔ Date) in docToWorkItem, createWorkItem, updateWorkItem.

2. Configurable "From" identities + dropdown

  • AppSettings.fromIdentities?: FromIdentity[] ({ id, name, email, isDefault }).
  • Settings: new "Email From Addresses" section to add / edit / remove identities and mark one default. Persists via existing updateSettings path. Helper text: each address must be a verified sender in Brevo or sending will fail.
  • EmailComposer: read-only From: line replaced with a <select> of settings.fromIdentities, preselecting the default (else first; else falls back to BRAND.fromEmail/fromName when none configured). Chosen identity is passed as fromEmail/fromName to sendCompletionEmail for both work-order and invoice emails.
  • Templates: extended only — saved template stores the chosen From (via existing saveEmailTemplate fromEmail/fromName), and loadTemplate restores the selection by matching the saved email to an identity. Template system not rebuilt.

3. Manual blank invoice creation

  • The "New Invoice" button + NewInvoiceModal + route already existed; the modal now stamps invoicedAt = now alongside invoiceStatus = 'draft' so manually-created invoices appear under Invoices and NOT in work-order lists.

4. Convert work order → invoice (per-item + bulk)

  • services/firestore.ts: new convertToInvoice(ids) — stamps invoicedAt = now, sets invoiceStatus = 'draft' only when unset (preserves existing status), does not touch send fields. Mirrors the bulk-update pattern.
  • WorkItemDetail: "Convert to Invoice" banner in the Invoice Status section, shown only when isWorkOrder(item).
  • WorkItems: bulk "Convert to Invoice" action alongside the existing bulk Archive.

Files changed (by part)

  • Part 1 (predicate): web/src/lib/types.ts, web/src/lib/workItem.ts, web/src/lib/workItem.test.ts, web/src/services/firestore.ts (converter + create/update)
  • Part 2 (From identities): web/src/lib/types.ts (FromIdentity, AppSettings), web/src/services/firestore.ts (subscribeSettings), web/src/routes/contractor/Settings.tsx, web/src/routes/contractor/EmailComposer.tsx
  • Part 3 (manual invoice): web/src/components/finance/NewInvoiceModal.tsx
  • Part 4 (convert): web/src/services/firestore.ts (convertToInvoice), web/src/routes/contractor/WorkItemDetail.tsx, web/src/routes/contractor/WorkItems.tsx

No new routes were added (the Invoices route + New Invoice modal already existed).

Caveat

From identities must be verified senders in Brevo or the sendCompletionEmail call will fail at send time. Surfaced as helper text in both Settings and the composer.

Test plan

  • npx tsc -b — exit 0
  • npx vitest run — 70/70 pass; workItem.test.ts extended to 11 tests covering invoicedAt, backward-compat, mutual exclusivity, and convert-to-invoice effect
  • npm run lint — no NEW errors/warnings in authored code; only pre-existing NewInvoiceModal set-state-in-effect error and firestore any warnings remain (present on main)
  • Manual: create blank invoice → appears in Invoices, absent from Work Orders
  • Manual: convert a work order (per-item + bulk) → moves to Invoices
  • Manual: add From identities in Settings, send work-order & invoice emails using the dropdown, save/load a template and confirm From is restored

…gurable From identities

Add explicit invoicedAt domain field so manually-created and converted invoices classify correctly (backward compatible with sent-date/status). Add configurable From identities in Settings + dropdown in EmailComposer. Add convertToInvoice service with per-item and bulk UI.
@github-actions

Copy link
Copy Markdown

🔥 Firebase Hosting preview: https://open-ten99--pr-49-lbmo2y7i.web.app
(expires in 7 days)

@Dev869
Dev869 merged commit 3f78e0b into main May 19, 2026
3 checks passed
@Dev869
Dev869 deleted the feat/invoices-and-from-identities branch May 19, 2026 02:49
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