WFDASH-WEBAPP/src/lib/types.ts

401 lines
11 KiB
TypeScript
Raw Normal View History

export interface BaseRecord {
id: string;
created: string;
updated: string;
collectionId: string;
collectionName: string;
}
export interface Customer extends BaseRecord {
customer: string;
email: string;
alternative_email: string;
buyer_email: string;
number: string;
company: string;
dateadded: string;
lastvisiteddate: string;
rep: string;
coordinates: string;
description: string;
}
export interface Company extends BaseRecord {
company: string;
address: string;
vendor: string;
}
export interface CompanyDetails extends BaseRecord {
company_name: string;
address: string;
number: string;
email: string;
vat_number: string;
registration_number: string;
}
export interface Supplier extends BaseRecord {
suppliername: string;
email: string;
suppliernumber: string;
supplieraddress: string;
supply_tags: string;
coordinates: string;
closingtime: string;
}
export type QuoteStatus =
| "new"
| "claimed"
| "processed"
| "emailed"
| "approval_pending"
| "approved"
| "rejected"
| "problem"
| "complete"
| "cancelled"
| "follow_up"
| "email_failed"
| "unmatched_bounce"
| "template"
/**
* Transient: the operator clicked Approve & Send Email. Next.js
* set this; n8n is about to (or currently) attempting the SMTP
* send. Cleared on success (→ emailed) or failure (→ mail_failed).
* Visible in the UI as a spinner pill. If the quote sits here for
* more than ~2 min the UI flags it as stuck.
*/
| "sending"
/**
* Terminal: the outbound send was attempted and failed. The
* reason is on `email_error`. Operator can retry from the modal,
* which flips status back to `sending`. Distinct from
* `email_failed` (which is the inbound bounce path).
*/
| "mail_failed";
export interface Quote extends BaseRecord {
quote_number: string;
quote_reference: string;
external_number: string;
rfq_number: string;
markup: number;
customer: string;
company: string;
claimed_by: string;
description: string;
status: QuoteStatus;
approved_by: string;
notes: string;
email_sender: string;
email_subject: string;
email_body: string;
has_attachments: boolean;
company_letterhead: string;
email_delivered: boolean;
email_delivered_at: string;
/** When the original inbound email was received (Date header / IMAP envelope date). */
email_received_at: string;
email_opened: boolean;
email_opened_at: string;
email_tracking_id: string;
has_new_reply: boolean;
last_reply_at: string;
email_failed_at: string;
pdf_generated_at: string;
email_message_id: string;
/** Date printed on the PDF (the quote's "official" date). Blank → fall back to `created`. */
quote_date: string;
/** Validity period in days (e.g. 30, 60). Blank/null → default to QUOTE_VALID_DAYS in the PDF. */
validity_days: number | null;
/** True for templates — reusable scaffolding, not a real customer quote. */
is_template: boolean;
/** If this template was cloned from a real quote, the source quote id. */
template_source_id: string;
/** If this quote was generated from a template, the template quote id. */
generated_from_template_id: string;
/** User-set follow-up reminder date. Dashboard widget flags overdue. */
follow_up_due: string;
/**
* Email-composition fields persisted by the Approve & Send modal
* (added 2026-06-23). Each is the operator's last-used value for
* this quote — so opening the modal a second time shows what they
* sent before, instead of starting from scratch.
*
* `email_subject_outbound` overrides the default `Re: {email_subject}`;
* `email_message` overrides the default "please find attached" body.
* Both empty falls back to the defaults at send time (n8n side).
*
* `attach_auth_letters` defaults true — toggles the per-letterhead
* supplier authorization letter (CNL or ISHERWOOD). Requires the
* `letterhead_settings.auth_letter` field to be uploaded; otherwise
* it's a no-op and the modal surfaces a note.
*/
email_cc: string;
email_bcc: string;
email_subject_outbound: string;
email_message: string;
attach_auth_letters: boolean;
/**
* RFC 5322 Message-ID stamped on the outbound email by the SMTP
* send. Lets the inbound monitor match future bounce / DSN
* messages back to this specific outbound attempt. Empty until
* the first send completes.
*/
email_outbound_message_id: string;
/**
* JSON array of `quote_attachments` record IDs that were attached
* to the outbound send. Captured at send time so retry sends the
* same set (not whatever is attached at retry time). Empty until
* the first send completes.
*/
email_outbound_attachment_ids: string;
/**
* Last failure reason from the outbound send. Empty on success.
* Cleared on every retry attempt. Visible in the modal banner
* and the quote card timeline.
*/
email_error: string;
/**
* Wall-clock when status flipped to `sending`. Lets the UI flag
* "stuck for >2 min" sends and operators measure SMTP turnaround.
*/
sending_started_at: string;
expand?: {
customer?: Customer;
company?: Company;
};
}
export type QuoteAttachmentKind = "pdf" | "image" | "doc" | "other";
/**
* Where a `quote_attachments` row came from / where it's going.
* - "inbound" — file arrived with the original RFQ email
* - "outbound" — operator uploaded it on the quote detail page to
* be sent with the quote email
* - "generated" — PDF rendered by `/api/v1/quotes/[id]/pdf`
*
* `direction` is null/undefined for rows that pre-date the
* 2026-06-23 migration; the UI must tolerate that and treat them
* as "inbound" by default (the migration backfills new rows, but
* very old rows may still be null in dev).
*/
export type QuoteAttachmentDirection = "inbound" | "outbound" | "generated";
export interface QuoteAttachment extends BaseRecord {
quote: string;
/**
* User-facing filename, e.g. "Quote-CNL-Q009638.pdf" — what we set
* when uploading. NOT what PB stores on disk; PB normalises to a
* slugged version in `file`.
*/
filename: string;
/**
* PB's stored file tokens. For a single-file field PB returns an
* array (it allows multiple per record); we treat it as an array
* here. The first element is the token to use in `/api/files/...` URLs.
*/
file: string[];
content_type: string;
size_bytes: number;
email_message_id: string;
/**
* Classification used by the dashboard to pick the right icon / preview.
* Older attachments (and a few mid-2026 imports) were uploaded without
* `kind` being set — the UI must tolerate `null` / `undefined`.
*/
kind?: QuoteAttachmentKind | null;
uploaded_at?: string;
/**
* See `QuoteAttachmentDirection`. Tolerate null/undefined for
* rows created before the 2026-06-23 direction-field migration.
*/
direction?: QuoteAttachmentDirection | null;
expand?: {
quote?: Quote;
};
}
export interface QuoteReply extends BaseRecord {
quote: string;
from_email: string;
from_name: string;
subject: string;
body_plain: string;
body_html: string;
received_at: string;
email_message_id: string;
in_reply_to: string;
quoted_text: string;
expand?: {
quote?: Quote;
};
}
export interface QuoteItem extends BaseRecord {
quote: string;
description: string;
quote_number: string;
quote_reference: string;
quantity: number;
supplier: string;
cost_price: number;
selling_price: number;
notes: string;
is_processed: boolean;
markup: number;
quote_pdf_id: string;
/**
* Flipped to true the first time the user saves the item from the
* process page or quote-form. Gates the AI rewrite feature — we
* don't let the LLM rewrite an item the user hasn't put their own
* touch on yet (the AI-extracted descriptions from the email
* pipeline are treated as drafts until a human confirms them).
*/
user_edited: boolean;
expand?: {
supplier?: Supplier;
};
}
export type OrderStatus =
| "new"
| "processing"
| "order_ready"
| "po_generated"
| "completed"
| "cancelled";
export type ItemStatus =
| "pending"
| "processed"
| "po_generated"
| "driver_pool"
| "assigned"
| "collected"
| "stock_verified"
| "picking"
| "ready_delivery"
| "delivered"
| "cancelled";
export interface Order extends BaseRecord {
order_number: string;
company: string;
customer: string;
quote: string;
status: OrderStatus;
notes: string;
quote_matching_attempted: boolean;
quote_match_confidence: number;
potential_quote: string;
potential_quote_confidence: number;
is_repair_order: boolean;
expand?: {
company?: Company;
customer?: Customer;
quote?: Quote;
};
}
export interface OrderItem extends BaseRecord {
order: string;
description: string;
po_description: string;
quantity: number;
order_qty: number;
selling_price: number;
supplier: string;
cost_price: number;
markup: number;
purchase_order: string;
item_status: ItemStatus;
drv_status?: "unassigned" | "assigned" | "en_route" | "collected" | "failed";
drv_id?: string;
notes: string;
expand?: {
supplier?: Supplier;
};
}
export interface CollectionPool extends BaseRecord {
order: string;
order_item: string;
supplier?: string;
drv_id?: string;
pool_status: "queued" | "assigned" | "in_progress" | "completed" | "failed";
priority?: "low" | "normal" | "high" | "urgent";
pool_notes?: string;
assigned_at?: string;
completed_at?: string;
expand?: {
order?: Order;
order_item?: OrderItem;
supplier?: Supplier;
};
}
export interface PurchaseOrder extends BaseRecord {
po_number: string;
order: string;
supplier: string;
status: string;
total_amount: number;
expand?: {
order?: Order;
supplier?: Supplier;
};
}
export interface DeliveryNote extends BaseRecord {
delivery_note_number: string;
order: string;
company?: string;
customer?: string;
status: "draft" | "ready" | "delivered" | "cancelled";
delivery_date?: string;
notes?: string;
expand?: {
order?: Order;
company?: Company;
customer?: Customer;
};
}
export interface DeliveryNoteItem extends BaseRecord {
delivery_note: string;
order_item: string;
description: string;
quantity: number;
expand?: {
delivery_note?: DeliveryNote;
order_item?: OrderItem;
};
}
export interface Repair extends BaseRecord {
repair_number: string;
customer?: string;
company?: string;
order?: string;
status: "new" | "assessing" | "quoted" | "approved" | "in_progress" | "completed" | "cancelled";
item_description: string;
fault_description: string;
notes?: string;
expand?: {
customer?: Customer;
company?: Company;
order?: Order;
};
}
export interface DashboardStats {
totalCustomers: number;
activeQuotes: number;
ordersThisMonth: number;
totalRevenue: number;
}