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:
3
backend/app/services/psa/autotask/__init__.py
Normal file
3
backend/app/services/psa/autotask/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from app.services.psa.autotask.provider import AutotaskProvider
|
||||
|
||||
__all__ = ["AutotaskProvider"]
|
||||
72
backend/app/services/psa/autotask/provider.py
Normal file
72
backend/app/services/psa/autotask/provider.py
Normal 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")
|
||||
3
backend/app/services/psa/halopsa/__init__.py
Normal file
3
backend/app/services/psa/halopsa/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from app.services.psa.halopsa.provider import HaloPSAProvider
|
||||
|
||||
__all__ = ["HaloPSAProvider"]
|
||||
72
backend/app/services/psa/halopsa/provider.py
Normal file
72
backend/app/services/psa/halopsa/provider.py
Normal 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")
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user