feat(enterprise): add multi-PSA adapter stubs for Autotask and Halo PSA

- Create AutotaskProvider stub with all abstract methods raising NotImplementedError
- Create HaloPSAProvider stub with all abstract methods raising NotImplementedError
- Add PSA provider grid in IntegrationsPage showing ConnectWise (active),
  Autotask (Coming Soon), and Halo PSA (Coming Soon) with appropriate badges

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-19 20:35:39 +00:00
parent 2f56327f81
commit 21d6d09c05
5 changed files with 202 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
from app.services.psa.autotask.provider import AutotaskProvider
__all__ = ["AutotaskProvider"]

View File

@@ -0,0 +1,72 @@
"""Autotask PSA provider stub. Full implementation planned for Phase 5."""
from __future__ import annotations
from app.services.psa.base import PSAProvider
from app.services.psa.types import (
ConnectionTestResult,
PSATicket,
PSANote,
PSAStatus,
PSACompany,
PSAMember,
PSAConfiguration,
PSATimeEntry,
)
class AutotaskProvider(PSAProvider):
"""Stub provider for Autotask PSA. All methods raise NotImplementedError.
Full implementation is planned for Phase 5. This stub allows the provider
to be registered and discovered without causing import errors.
"""
async def test_connection(self) -> ConnectionTestResult:
raise NotImplementedError("Autotask integration coming soon")
async def get_ticket(self, ticket_id: str) -> PSATicket:
raise NotImplementedError("Autotask integration coming soon")
async def search_tickets(self, query: str, **filters) -> list[PSATicket]:
raise NotImplementedError("Autotask integration coming soon")
async def post_note(
self,
ticket_id: str,
text: str,
note_type: str,
member_id: str | None = None,
) -> PSANote:
raise NotImplementedError("Autotask integration coming soon")
async def update_ticket_status(
self,
ticket_id: str,
status_id: int,
) -> PSATicket:
raise NotImplementedError("Autotask integration coming soon")
async def get_ticket_statuses(self, board_id: int) -> list[PSAStatus]:
raise NotImplementedError("Autotask integration coming soon")
async def list_companies(self, **filters) -> list[PSACompany]:
raise NotImplementedError("Autotask integration coming soon")
async def get_company(self, company_id: str) -> PSACompany:
raise NotImplementedError("Autotask integration coming soon")
async def list_members(self) -> list[PSAMember]:
raise NotImplementedError("Autotask integration coming soon")
async def get_ticket_configurations(self, ticket_id: str) -> list[PSAConfiguration]:
raise NotImplementedError("Autotask integration coming soon")
async def create_time_entry(
self,
ticket_id: str,
member_id: str,
hours: float,
notes: str | None = None,
work_type: str | None = None,
) -> PSATimeEntry:
raise NotImplementedError("Autotask integration coming soon")

View File

@@ -0,0 +1,3 @@
from app.services.psa.halopsa.provider import HaloPSAProvider
__all__ = ["HaloPSAProvider"]

View File

@@ -0,0 +1,72 @@
"""Halo PSA provider stub. Full implementation planned for Phase 5."""
from __future__ import annotations
from app.services.psa.base import PSAProvider
from app.services.psa.types import (
ConnectionTestResult,
PSATicket,
PSANote,
PSAStatus,
PSACompany,
PSAMember,
PSAConfiguration,
PSATimeEntry,
)
class HaloPSAProvider(PSAProvider):
"""Stub provider for Halo PSA. All methods raise NotImplementedError.
Full implementation is planned for Phase 5. This stub allows the provider
to be registered and discovered without causing import errors.
"""
async def test_connection(self) -> ConnectionTestResult:
raise NotImplementedError("Halo PSA integration coming soon")
async def get_ticket(self, ticket_id: str) -> PSATicket:
raise NotImplementedError("Halo PSA integration coming soon")
async def search_tickets(self, query: str, **filters) -> list[PSATicket]:
raise NotImplementedError("Halo PSA integration coming soon")
async def post_note(
self,
ticket_id: str,
text: str,
note_type: str,
member_id: str | None = None,
) -> PSANote:
raise NotImplementedError("Halo PSA integration coming soon")
async def update_ticket_status(
self,
ticket_id: str,
status_id: int,
) -> PSATicket:
raise NotImplementedError("Halo PSA integration coming soon")
async def get_ticket_statuses(self, board_id: int) -> list[PSAStatus]:
raise NotImplementedError("Halo PSA integration coming soon")
async def list_companies(self, **filters) -> list[PSACompany]:
raise NotImplementedError("Halo PSA integration coming soon")
async def get_company(self, company_id: str) -> PSACompany:
raise NotImplementedError("Halo PSA integration coming soon")
async def list_members(self) -> list[PSAMember]:
raise NotImplementedError("Halo PSA integration coming soon")
async def get_ticket_configurations(self, ticket_id: str) -> list[PSAConfiguration]:
raise NotImplementedError("Halo PSA integration coming soon")
async def create_time_entry(
self,
ticket_id: str,
member_id: str,
hours: float,
notes: str | None = None,
work_type: str | None = None,
) -> PSATimeEntry:
raise NotImplementedError("Halo PSA integration coming soon")

View File

@@ -259,6 +259,58 @@ export function IntegrationsPage() {
{/* Connection Tab */}
{activeTab === 'connection' && (
<div className="max-w-3xl">
{/* PSA Provider Grid */}
<div className="mb-6">
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground mb-3">
Available PSA Integrations
</p>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
{/* ConnectWise — active */}
<div className={cn(
'rounded-xl border p-4 flex flex-col gap-2',
connection
? 'border-primary/40 bg-primary/5'
: 'border-border bg-card/30'
)}>
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-foreground">ConnectWise PSA</span>
{connection ? (
<span className="inline-flex items-center rounded-full bg-emerald-400/10 px-2 py-0.5 text-[0.625rem] font-label text-emerald-400">
Connected
</span>
) : (
<span className="inline-flex items-center rounded-full bg-primary/10 px-2 py-0.5 text-[0.625rem] font-label text-primary">
Available
</span>
)}
</div>
<p className="text-xs text-muted-foreground">Manage, ticket linking, time entries</p>
</div>
{/* Autotask — coming soon */}
<div className="rounded-xl border border-border bg-card/20 p-4 flex flex-col gap-2 opacity-60 cursor-not-allowed select-none">
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-foreground">Autotask PSA</span>
<span className="inline-flex items-center rounded-full bg-amber-400/10 px-2 py-0.5 text-[0.625rem] font-label text-amber-400">
Coming Soon
</span>
</div>
<p className="text-xs text-muted-foreground">Datto / Kaseya integration</p>
</div>
{/* Halo PSA — coming soon */}
<div className="rounded-xl border border-border bg-card/20 p-4 flex flex-col gap-2 opacity-60 cursor-not-allowed select-none">
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-foreground">Halo PSA</span>
<span className="inline-flex items-center rounded-full bg-amber-400/10 px-2 py-0.5 text-[0.625rem] font-label text-amber-400">
Coming Soon
</span>
</div>
<p className="text-xs text-muted-foreground">HaloPSA / HaloITSM integration</p>
</div>
</div>
</div>
{/* Illustrative empty state when no connection exists */}
{mode === 'setup' && (
<div className="mb-6">