feat: email receipts and repair estimates
Backend: - Server-side HTML email templates (receipt + estimate) with inline CSS - POST /v1/transactions/:id/email-receipt with per-transaction rate limiting - POST /v1/repair-tickets/:id/email-estimate with per-ticket rate limiting - customerEmail field added to receipt and ticket detail responses - Test email provider for API tests (logs instead of sending) Frontend: - POS payment dialog Email button enabled with inline email input - Pre-fills customer email from linked account - Repair ticket detail page has Email Estimate button with dialog - Pre-fills from account email Tests: - 12 unit tests for email template renderers - 8 API tests for email receipt/estimate endpoints and validation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
import { products, inventoryUnits } from '../db/schema/inventory.js'
|
||||
import { repairTickets, repairLineItems } from '../db/schema/repairs.js'
|
||||
import { companies, locations } from '../db/schema/stores.js'
|
||||
import { accounts } from '../db/schema/accounts.js'
|
||||
import { NotFoundError, ValidationError, ConflictError } from '../lib/errors.js'
|
||||
import { TaxService } from './tax.service.js'
|
||||
import type {
|
||||
@@ -473,8 +474,16 @@ export const TransactionService = {
|
||||
location = loc ?? null
|
||||
}
|
||||
|
||||
// Resolve customer email from linked account
|
||||
let customerEmail: string | null = null
|
||||
if (txn.accountId) {
|
||||
const [acct] = await db.select({ email: accounts.email }).from(accounts).where(eq(accounts.id, txn.accountId)).limit(1)
|
||||
customerEmail = acct?.email ?? null
|
||||
}
|
||||
|
||||
return {
|
||||
transaction: txn,
|
||||
customerEmail,
|
||||
company: company
|
||||
? {
|
||||
name: company.name,
|
||||
|
||||
Reference in New Issue
Block a user