diff --git a/api/migrations/086_state_trucking_fulfillment_status.sql b/api/migrations/086_state_trucking_fulfillment_status.sql new file mode 100644 index 0000000..7838609 --- /dev/null +++ b/api/migrations/086_state_trucking_fulfillment_status.sql @@ -0,0 +1,46 @@ +-- 086: State motor carrier fulfillment status machine. +-- +-- State trucking filings (IRP, IFTA, NY HUT, CT HUF, weight-distance, CA MCP, +-- etc.) have a multi-step fulfillment lifecycle that goes beyond payment: we +-- must collect the customer's signed authorization, possibly get them to add us +-- as a delegate inside the state portal (or collect credentials), get approval +-- for variable government fees, and then file and wait on the state. +-- +-- This adds a dedicated fulfillment_status column to compliance_orders so ops +-- and the customer portal can see exactly where a state filing is, independent +-- of payment_status. Non-state services simply leave it NULL. +-- +-- Status lifecycle (see docs/trucking-state-authorization-plan.md): +-- authorization_required -> we emailed the customer the signing link +-- authorization_signed -> customer signed the Limited Authorization +-- awaiting_customer_delegation -> customer must add us as portal user/delegate +-- awaiting_secure_credentials -> delegation unsupported; collecting credentials +-- awaiting_government_fee_approval -> variable state fee quoted, awaiting OK to pay +-- awaiting_insurance_filing -> waiting on insurer to file proof (e.g. intrastate) +-- ready_to_file -> all blockers cleared; queued for filing +-- filed_waiting_state -> submitted; waiting on the state to issue +-- completed -> credentials/decals/account delivered + +ALTER TABLE compliance_orders + ADD COLUMN IF NOT EXISTS fulfillment_status TEXT + CHECK (fulfillment_status IS NULL OR fulfillment_status IN ( + 'authorization_required', + 'authorization_signed', + 'awaiting_customer_delegation', + 'awaiting_secure_credentials', + 'awaiting_government_fee_approval', + 'awaiting_insurance_filing', + 'ready_to_file', + 'filed_waiting_state', + 'completed' + )), + ADD COLUMN IF NOT EXISTS fulfillment_status_at TIMESTAMPTZ; + +CREATE INDEX IF NOT EXISTS idx_compliance_orders_fulfillment + ON compliance_orders(fulfillment_status) + WHERE fulfillment_status IS NOT NULL; + +COMMENT ON COLUMN compliance_orders.fulfillment_status IS + 'State motor carrier filing lifecycle (NULL for services without a portal/authorization step). See migration 086.'; +COMMENT ON COLUMN compliance_orders.fulfillment_status_at IS + 'When fulfillment_status last changed.'; diff --git a/api/src/routes/compliance-orders.ts b/api/src/routes/compliance-orders.ts index b254528..1cc678d 100644 --- a/api/src/routes/compliance-orders.ts +++ b/api/src/routes/compliance-orders.ts @@ -295,81 +295,101 @@ const COMPLIANCE_SERVICES: Record< discountable: true, }, // ── State-Level Trucking Compliance ────────────────────────────────── + // For these services the price_cents is our flat SERVICE fee only. The + // actual state government charges (apportioned IRP registration, IFTA decal + // fees, weight-distance/HUT account setup, permit/decal costs) vary by the + // carrier's fleet weight, mileage, and base state, so they are passed + // through AT COST and billed separately once the state issues the invoice — + // we never mark them up. The gov_fee_label is shown to the customer at + // checkout so the pass-through is disclosed up front. "irp-registration": { name: "IRP Registration Assistance", price_cents: 10900, // + state fees (apportioned registration billed at cost) + gov_fee_label: "Apportioned IRP registration & plate fees (state, by jurisdictions + weight, billed at cost)", erpnext_item: "IRP-REGISTRATION", discountable: true, }, "ifta-application": { name: "IFTA Application + Decals", price_cents: 10900, // + state fees + gov_fee_label: "IFTA license & decal fees (state, billed at cost)", erpnext_item: "IFTA-APPLICATION", discountable: true, }, "ifta-quarterly": { name: "IFTA Quarterly Filing", price_cents: 10900, + gov_fee_label: "IFTA taxes due (remitted to the state at cost based on your mileage)", erpnext_item: "IFTA-QUARTERLY", discountable: true, }, "or-weight-mile-tax": { name: "Oregon Weight-Mile Tax Setup", price_cents: 10900, // + state fees + gov_fee_label: "Oregon weight-mile tax account & bond/deposit (state, billed at cost)", erpnext_item: "OR-WEIGHT-MILE-TAX", discountable: true, }, "ny-hut-registration": { name: "NY Highway Use Tax Registration", price_cents: 10900, // + state fees + gov_fee_label: "NY HUT certificate & decal fees (state, billed at cost)", erpnext_item: "NY-HUT-REGISTRATION", discountable: true, }, "ky-kyu-registration": { name: "KY Weight-Distance Tax Setup", price_cents: 10900, // + state fees + gov_fee_label: "KYU weight-distance account fees (state, billed at cost)", erpnext_item: "KY-KYU-REGISTRATION", discountable: true, }, "nm-weight-distance": { name: "NM Weight-Distance Tax Setup", price_cents: 10900, // + state fees + gov_fee_label: "NM weight-distance permit & account fees (state, billed at cost)", erpnext_item: "NM-WEIGHT-DISTANCE", discountable: true, }, "ct-highway-use-fee": { name: "CT Highway Use Fee Setup", price_cents: 10900, // + state fees + gov_fee_label: "CT Highway Use Fee registration (state, billed at cost)", erpnext_item: "CT-HIGHWAY-USE-FEE", discountable: true, }, "ca-mcp-carb": { name: "California MCP + CARB Compliance", price_cents: 22900, // $229 + state fees (CA is more complex: MCP + CARB) + gov_fee_label: "CA MCP permit fee + CARB/Clean Truck Check fees (state, by fleet size, billed at cost)", erpnext_item: "CA-MCP-CARB", discountable: true, }, "state-dot-registration": { name: "State DOT Registration", price_cents: 10900, // + state fees + gov_fee_label: "State DOT/intrastate registration fee (state, billed at cost)", erpnext_item: "STATE-DOT-REGISTRATION", discountable: true, }, "intrastate-authority": { name: "Intrastate Operating Authority", price_cents: 10900, // + state fees + gov_fee_label: "State intrastate authority filing fee (state, billed at cost)", erpnext_item: "INTRASTATE-AUTHORITY", discountable: true, }, "osow-permit": { name: "Oversize/Overweight Permit", price_cents: 10900, // + state permit fees + gov_fee_label: "State oversize/overweight permit fees (per trip/route, billed at cost)", erpnext_item: "OSOW-PERMIT", discountable: true, }, "state-trucking-bundle": { name: "State Compliance Bundle", price_cents: 49900, // $499 — IRP+IFTA+weight tax+intrastate ($796 individual) + gov_fee_label: "State registration, decal & tax fees for each included filing (billed at cost)", erpnext_item: "STATE-TRUCKING-BUNDLE", discountable: true, }, diff --git a/scripts/workers/services/state_trucking.py b/scripts/workers/services/state_trucking.py index 091c4ab..1311832 100644 --- a/scripts/workers/services/state_trucking.py +++ b/scripts/workers/services/state_trucking.py @@ -32,6 +32,19 @@ from datetime import datetime LOG = logging.getLogger("workers.services.state_trucking") +# State motor carrier fulfillment lifecycle (compliance_orders.fulfillment_status, +# migration 086). These let ops and the customer portal see exactly where a state +# filing is, independent of payment_status. +FULFILLMENT_AUTHORIZATION_REQUIRED = "authorization_required" +FULFILLMENT_AUTHORIZATION_SIGNED = "authorization_signed" +FULFILLMENT_AWAITING_DELEGATION = "awaiting_customer_delegation" +FULFILLMENT_AWAITING_CREDENTIALS = "awaiting_secure_credentials" +FULFILLMENT_AWAITING_FEE_APPROVAL = "awaiting_government_fee_approval" +FULFILLMENT_AWAITING_INSURANCE = "awaiting_insurance_filing" +FULFILLMENT_READY_TO_FILE = "ready_to_file" +FULFILLMENT_FILED_WAITING_STATE = "filed_waiting_state" +FULFILLMENT_COMPLETED = "completed" + # Map service slugs to human-readable names and filing instructions SERVICE_INFO = { "irp-registration": { @@ -258,6 +271,7 @@ class StateTruckingHandler: signer_title=str(intake.get("signer_title", "")), ) if requested: + self._set_fulfillment_status(order_number, FULFILLMENT_AUTHORIZATION_REQUIRED) LOG.info( "[%s] Authorization requested — pipeline PAUSED pending signature", order_number, @@ -271,6 +285,7 @@ class StateTruckingHandler: ) else: signed_auth_key = self._signed_authorization_key(order_number) + self._set_fulfillment_status(order_number, FULFILLMENT_AUTHORIZATION_SIGNED) LOG.info("[%s] Authorization signed (%s) — proceeding to filing", order_number, signed_auth_key) # Slug-specific intake fields collected by StateTruckingIntakeStep. @@ -343,6 +358,8 @@ class StateTruckingHandler: finally: conn.close() LOG.info("[%s] Admin todo created for %s", order_number, service_name) + # Authorization is in hand and the filing task is queued for ops. + self._set_fulfillment_status(order_number, FULFILLMENT_READY_TO_FILE) except Exception as exc: LOG.error("[%s] Failed to create admin todo: %s", order_number, exc) @@ -606,6 +623,30 @@ class StateTruckingHandler: return True + def _set_fulfillment_status(self, order_number: str, status: str) -> None: + """Advance compliance_orders.fulfillment_status (best-effort). + + ``status`` must be one of the FULFILLMENT_* constants. Non-fatal: a + failure here never blocks the filing pipeline. + """ + try: + import psycopg2 + conn = psycopg2.connect(os.environ.get("DATABASE_URL", "")) + try: + with conn.cursor() as cur: + cur.execute( + """UPDATE compliance_orders + SET fulfillment_status = %s, fulfillment_status_at = NOW(), + updated_at = NOW() + WHERE order_number = %s""", + (status, order_number), + ) + conn.commit() + finally: + conn.close() + except Exception as exc: + LOG.warning("[%s] Could not set fulfillment_status=%s: %s", order_number, status, exc) + def _authorization_status(self, order_number: str) -> str | None: """Return the current authorization esign status: 'signed', 'pending', or None.""" try: diff --git a/site/src/components/TruckingValueNotice.astro b/site/src/components/TruckingValueNotice.astro index 16c7984..fad4d01 100644 --- a/site/src/components/TruckingValueNotice.astro +++ b/site/src/components/TruckingValueNotice.astro @@ -223,6 +223,35 @@ const n = NOTICES[slug];

+ +
+ What's included & what's billed at cost +
+

+ The price shown is our flat service fee to prepare and + submit this filing for you. Government charges are passed through + at cost — we never mark them up. Depending on your + fleet weight, mileage, and base state, the state may charge: +

+ +

+ Where these amounts are not known at checkout, we bill them separately at + cost once the state issues its invoice — with your approval before + we remit anything. +

+

+ To file on your behalf we will email you a short authorization to + sign. It lets Performance West prepare and submit your filing, + communicate with the agency, and remit the government fees you provide. If + the state requires it, we may ask you to add us as a portal user/delegate + or securely share account access. +

+
+
)}