// @proposal-system/api-contracts — single source of truth for API wire types. // Fix: WEB-M5 — web and mobile both import these; shapes mirror the .NET DTOs // in api/src/ProposalSystem.Application/DTOs/ (JsonStringEnumConverter → string // enums, Guid → string, DateTime → ISO string, decimal → number). // Runtime validation schemas live in ./schemas (separate entrypoint so // consumers that only need types never pull zod). // ── Enums (string over the wire) ────────────────────────────────────────── export type ProposalStatus = 'Draft' | 'InReview' | 'Approved' | 'Sent' | 'Revised'; 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'; // ── Shared envelopes ────────────────────────────────────────────────────── export interface PagedResponse { items: T[]; totalCount: number; page: number; pageSize: number; } // ── Proposals (ProposalDtos.cs) ─────────────────────────────────────────── export interface ProposalListItem { id: string; proposalNumber: string; customerName: string; workOrderNumber: string; serviceCategory: ServiceCategory; priority: Priority; status: ProposalStatus; totalBidAmount: number; submittedAt: string; submittedByName: string | null; assignedAdminName: string | null; } export interface ProposalDetail { id: string; proposalNumber: string; workOrderNumber: string; poNumber: string | null; customerName: string; customerAddress: string; scopeOfWork: string; refinedScope: string | null; serviceCategory: ServiceCategory; priority: Priority; status: ProposalStatus; totalBidAmount: number; vendorTotalCost: number | null; notes: string; submittedById: string; submittedByName: string | null; 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 CreateProposalRequest { workOrderNumber: string; poNumber?: string; customerName: string; customerAddress: string; scopeOfWork: string; serviceCategory: ServiceCategory; priority: Priority; notes?: string; } export interface UpdateProposalRequest { refinedScope?: string; notes?: string; poNumber?: string; workOrderNumber?: string; assignedAdminId?: string; } export interface ProposalFilters { search?: string; page?: number; pageSize?: number; status?: string; serviceCategory?: string; priority?: string; fromDate?: string; toDate?: string; mine?: boolean; } export interface ProposalStats { totalCount: number; inReviewCount: number; approvedCount: number; sentCount: number; } // ── Line items (LineItemDtos.cs) ────────────────────────────────────────── 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 CreateLineItemRequest { description: string; quantity: number; unit: string; unitPrice: number | null; totalPrice: number; pricingMode: PricingMode; sortOrder: number; source: LineItemSource; } export interface UpdateLineItemEntry { id: string | null; description: string; quantity: number; unit: string; unitPrice: number | null; totalPrice: number; pricingMode: PricingMode; sortOrder: number; source: LineItemSource; } export interface BulkUpdateLineItemsRequest { lineItems: UpdateLineItemEntry[]; } // ── Customers (CustomerDtos.cs) ─────────────────────────────────────────── export interface Customer { id: string; name: string; addresses: string[]; contactEmail: string | null; createdAt: string; } export interface CustomerListParams { page?: number; pageSize?: number; search?: string; } export interface CreateCustomerRequest { name: string; addresses?: string[]; contactEmail?: string; } export interface UpdateCustomerRequest { name?: string; addresses?: string[]; contactEmail?: string | null; } // ── Pricing library (PricingLibraryDtos.cs) ─────────────────────────────── 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 PricingLibraryListParams { page?: number; pageSize?: number; } export interface CreatePricingLibraryItemRequest { title: string; description?: string; serviceCategory: ServiceCategory; unit?: string; unitPrice?: number | null; keywords?: string; source?: string; } export interface UpdatePricingLibraryItemRequest { title?: string; description?: string; serviceCategory?: ServiceCategory; unit?: string; unitPrice?: number | null; keywords?: string; } // ── Admin (DashboardDtos.cs, AuditDtos.cs) ──────────────────────────────── export interface DashboardStats { pendingCount: number; approvedThisWeek: number; avgTurnaroundHours: number; totalProposals: number; } export interface AuditEntry { id: string; proposalId: string | null; userId: string; userName: string | null; action: string; details: string | null; timestamp: string; ipAddress: string | null; } // ── Sites (SiteDtos.cs) ─────────────────────────────────────────────────── export interface Site { siteCode: string; fullAddress: string | null; address: string | null; city: string | null; state: string | null; zip: string | null; } // ── Auth / users (UserDtos.cs, AuthController) ──────────────────────────── export interface AuthUser { id: string; email: string; displayName: string; role: UserRole; token: string; } export interface UserProfile { id: string; email: string; displayName: string; role: UserRole; } // ── Files / PDFs (FileDtos.cs) ──────────────────────────────────────────── export interface PdfVersion { revision: number; generatedAt: string; } export interface PresignedUpload { uploadUrl: string; s3Key: string; expiresAt: string; vendorProposalId: string; } // ── Errors (GlobalExceptionHandler.cs) ──────────────────────────────────── // RFC 7807 ProblemDetails + machine-readable business code (SHOC error-code // vocabulary convention). Branch on `code`, never on title/detail text. export type ApiProblemCode = | 'ValidationFailed' | 'InvalidStateTransition' | 'NotFound' | 'Unauthorized' | 'InternalError' | (string & {}); // business codes are open-ended (e.g. future CancelNotAllowed) export interface ApiProblem { status: number; title: string; detail: string; code: ApiProblemCode; }