proposal-system/shared/api-contracts/src/index.ts

123 lines
2.9 KiB
TypeScript
Raw Normal View History

export type ProposalStatus = 'Draft' | 'InReview' | 'Approved' | 'Sent' | 'Revised';
// Fix: WEB-M4 — align with API enum (ProposalSystem.Domain.Entities.ServiceCategory includes 'Other')
export type ServiceCategory = 'HVAC' | 'Plumbing' | 'Electrical' | 'General' | 'Renovation' | 'Other';
export type Priority = 'Standard' | 'Urgent' | 'Emergency';
export type LineItemSource = 'AI' | 'Vendor' | 'Manual' | 'Historical';
export type PricingMode = 'UnitPrice' | 'TotalPrice' | 'Both';
export type UserRole = 'Dispatcher' | 'Admin' | 'SysAdmin';
export interface Proposal {
id: string;
proposalNumber: string;
workOrderNumber: string;
customerName: string;
customerAddress: string;
scopeOfWork: string;
refinedScope: string | null;
serviceCategory: ServiceCategory;
priority: Priority;
status: ProposalStatus;
totalBidAmount: number;
vendorTotalCost: number | null;
notes: string;
submittedById: string;
submittedAt: string;
assignedAdminId: string | null;
approvedById: string | null;
approvedAt: string | null;
sentAt: string | null;
currentRevision: number;
parentProposalId: string | null;
createdAt: string;
updatedAt: string;
}
export interface LineItem {
id: string;
proposalId: string;
description: string;
quantity: number;
unit: string;
unitPrice: number | null;
totalPrice: number;
pricingMode: PricingMode;
sortOrder: number;
source: LineItemSource;
createdAt: string;
updatedAt: string;
}
export interface CreateProposalRequest {
workOrderNumber: string;
customerName: string;
customerAddress: string;
scopeOfWork: string;
serviceCategory: ServiceCategory;
priority: Priority;
notes?: string;
}
export interface UpdateLineItemsRequest {
lineItems: Omit<LineItem, 'id' | 'proposalId' | 'createdAt' | 'updatedAt'>[];
}
feat: proposal delivery — email customers the PDF on Mark as Sent (#126) * feat: proposal delivery — email customers the PDF on "Mark as Sent" Makes the system's namesake feature real: marking a proposal Sent now emails the customer an expiring link to the branded PDF, and customers are managed (with contact emails) instead of hardcoded. v1 PR4. API: - Customer.ContactEmail + migration; Customer list/update endpoints. Search stays additive at GET /api/customers?query= (frozen-mobile + web compat); new paginated list at GET /api/customers/list (admin). - IEmailService (SesEmailService v2 / DevEmailService, dev-gated). MarkSentAsync resolves the customer's email, presigns the latest PDF (7d), and sends via SES. Email/presign failures are caught + audited and NEVER roll back the Sent transition. - Startup EF migration guarded by a Postgres advisory lock (concurrency-safe). Infra: - SES email identity (proposals@seahavenind.com); least-privilege ses:SendEmail/ SendRawEmail scoped to the identity ARN + ses:FromAddress condition; SES_FROM_ADDRESS env. SES starts in sandbox — production access needed for unverified recipients. Web: - Customer management page (/admin/customers): list / create / edit incl. contact email. - New-proposal form searches real customers (free-solo) instead of a hardcoded value. - Mark-as-Sent dialog notes the PDF will be emailed to the customer. GPT-4.1 cross-review (SES IAM): no BLOCK (ses:FromAddress condition applied). Verified: api build + 121 tests; web tsc + 26 tests; infra tsc; ruff clean. * Potential fix for pull request finding 'CodeQL / Exposure of private information' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Exposure of private information' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Exposure of private information' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-06-18 12:40:55 -04:00
export interface Customer {
id: string;
name: string;
addresses: string[];
contactEmail: string | null;
createdAt: string;
}
export interface CreateCustomerRequest {
name: string;
addresses?: string[];
contactEmail?: string;
}
export interface UpdateCustomerRequest {
name?: string;
addresses?: string[];
contactEmail?: string | null;
}
export interface PagedResponse<T> {
items: T[];
totalCount: number;
page: number;
pageSize: number;
}
// PR5: Pricing Library types
export interface PricingLibraryItem {
id: string;
title: string;
description: string | null;
serviceCategory: ServiceCategory;
unit: string | null;
unitPrice: number | null;
keywords: string | null;
source: string;
createdAt: string;
updatedAt: string;
}
export interface CreatePricingLibraryItemRequest {
title: string;
description?: string;
serviceCategory: ServiceCategory;
unit?: string;
unitPrice?: number;
keywords?: string;
source?: string;
}
export interface UpdatePricingLibraryItemRequest {
title?: string;
description?: string;
serviceCategory?: ServiceCategory;
unit?: string;
unitPrice?: number;
keywords?: string;
}