Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
245 lines
13 KiB
Markdown
245 lines
13 KiB
Markdown
# Hudu Integration — Phase 2: Assets & Documents
|
||
|
||
- Status: Draft (pending scope confirmation)
|
||
- Phase: 2 (still pull-only, Hudu → AlgaPSA)
|
||
- Predecessor: `ee/docs/plans/2026-06-08-hudu-integration` (Phase 1, complete)
|
||
|
||
## Overview
|
||
|
||
Phase 1 connected Hudu and surfaced a mapped company's assets, articles, and
|
||
passwords read-only on the client page. Phase 2 deepens two of those surfaces:
|
||
|
||
1. **Assets become first-class**: Hudu assets can be **mapped** to existing
|
||
AlgaPSA assets, **imported** (created in Alga from Hudu), and kept current
|
||
with a **manual pull sync** that updates synced fields on mapped assets.
|
||
2. **Articles meet Alga's Documents surfaces** (link-only, no content copy):
|
||
a "Hudu Documentation" section on the client's Documents tab, and a "Hudu"
|
||
tab on the main Documents page listing/searching articles across all
|
||
mapped companies.
|
||
|
||
Phase 1 principles that still hold: EE-only behind the `hudu-integration`
|
||
flag; pull-only; deep-link to Hudu for content; never persist article bodies
|
||
or passwords; `system_settings` RBAC for integration administration.
|
||
|
||
## Problem / User Value
|
||
|
||
- Technicians track devices in both systems by hand. A Hudu asset and its
|
||
Alga asset (used for tickets/billing) have no link, so device context is
|
||
re-discovered on every ticket.
|
||
- MSPs onboarding to AlgaPSA already have their device inventory in Hudu;
|
||
re-entering it manually is the single biggest adoption blocker for Alga's
|
||
asset module.
|
||
- Runbooks/KB articles live in Hudu but technicians work documents from
|
||
Alga's Documents surfaces; today they must remember which system holds what.
|
||
|
||
## Goals
|
||
|
||
- G1. Per-asset mapping between Hudu assets and Alga assets (per client).
|
||
- G2. One-click import of unmatched Hudu assets into Alga (single + bulk).
|
||
- G3. Manual "Sync from Hudu" per client that pull-updates synced fields on
|
||
mapped assets and flags Hudu-side disappearances.
|
||
- G4. Link-only "Hudu Documentation" section in the client Documents tab.
|
||
- G5. "Hudu" tab on the main Documents page: cross-client article list with
|
||
search, client resolution, and deep-links.
|
||
|
||
## Non-goals (Phase 2)
|
||
|
||
- **Push (Alga → Hudu)** in any form; the data model stays direction-agnostic.
|
||
- **Scheduled/background sync** (Temporal or otherwise). Sync is manual this
|
||
phase; a low-frequency scheduled refresh remains a documented future step.
|
||
- **Importing article content** as Alga documents (link-only by decision).
|
||
- **Custom-field sync** beyond the core field set (name, serial number,
|
||
asset type via layout map). Hudu layout custom fields are displayed in
|
||
Phase 1's read-only view only.
|
||
- **Deleting or archiving Alga assets** in response to Hudu changes — sync
|
||
never destroys Alga data; it only flags.
|
||
- Mapping Hudu passwords to assets (unchanged from Phase 1; company-scoped).
|
||
|
||
## Personas & Primary Flows
|
||
|
||
- **MSP Admin** — configures the asset-layout→asset-type map; runs bulk import.
|
||
- **MSP Technician** — maps/imports individual assets, runs Sync, browses
|
||
Hudu documentation from Documents surfaces.
|
||
|
||
Flows:
|
||
|
||
1. **Configure layout map** (Admin): Settings → Integrations → Hudu →
|
||
"Asset layouts" block → each Hudu asset layout gets an Alga asset type
|
||
(heuristic prefill, `unknown` fallback) → Save.
|
||
2. **Map/import assets** (Technician): Client → Hudu tab → Assets section now
|
||
shows per-asset state (Mapped / Suggested / Unmapped) with an Alga-asset
|
||
picker per row, an Import action per unmatched row, and "Import all
|
||
unmatched". Staged changes commit via an explicit **Save** bar (same
|
||
confirmation pattern as Phase 1 company mappings).
|
||
3. **Sync** (Technician): Client → Hudu tab → "Sync from Hudu" → re-fetches
|
||
the company's assets, updates synced fields on mapped Alga assets,
|
||
reports created/updated/stale counts.
|
||
4. **Client documents** (Technician): Client → Documents tab → "Hudu
|
||
Documentation" section lists the mapped company's articles (name,
|
||
updated_at) → click → opens in Hudu.
|
||
5. **Global documents** (Technician): Documents page → "Hudu" tab → paged,
|
||
searchable article list across companies; each row shows the resolved
|
||
Alga client (or "unmapped") and deep-links to Hudu.
|
||
|
||
## UX / UI Notes
|
||
|
||
- **Settings — Asset layouts block** (inside `HuduIntegrationSettings`, below
|
||
Company Mappings): table of Hudu asset layouts (fetched live) × Alga asset
|
||
type select (`workstation | network_device | server | mobile_device |
|
||
printer | unknown`). Heuristic prefill by layout-name keywords
|
||
(server→server; workstation/desktop/laptop→workstation; printer→printer;
|
||
phone/mobile/tablet→mobile_device; network/switch/router/firewall/access
|
||
point→network_device; else unknown). Explicit Save; persisted per tenant.
|
||
- **Client Hudu tab — Assets section** becomes an asset mapping manager,
|
||
visually consistent with the company mapping manager: counters
|
||
(mapped/suggested/unmapped), per-row Alga asset picker + status badge,
|
||
row actions (revert / unmap / dismiss suggestion), Import per row,
|
||
"Import all unmatched" button, staged-changes Save/Discard bar, and a
|
||
"Sync from Hudu" button with last-synced timestamp and result summary.
|
||
Mapped rows whose Hudu asset disappeared/archived show a **Stale** badge.
|
||
- **Client Documents tab — Hudu section**: collapsed-by-default section
|
||
"Hudu Documentation (N)" listing articles name + updated_at + external-link
|
||
icon. Only rendered when the Phase 1 client-tab gate passes (EE + flag +
|
||
connected + mapped). No upload/edit affordances.
|
||
- **Documents page — Hudu tab**: tab visible when EE + flag + connected.
|
||
Search box (server-side, passed to Hudu), paged table (25/page mirroring
|
||
Hudu pages): article name, company name → resolved client name (or
|
||
"Unmapped" badge), updated_at, open-in-Hudu. No bulk fetch of all pages.
|
||
- All new strings via i18n (en + de/es/fr/it/nl/pl/pt), following Phase 1
|
||
key families (`integrations.hudu.assets.*`, `integrations.hudu.documents.*`,
|
||
`documents.huduTab.*`, `clientDetails.huduDocs.*` as applicable).
|
||
|
||
## Functional Requirements
|
||
|
||
Assets — mapping:
|
||
- FR1. Asset mapping rows reuse `tenant_external_entity_mappings` with
|
||
`integration_type='hudu'`, `alga_entity_type='asset'`,
|
||
`external_entity_id=<hudu asset id>`; metadata carries
|
||
`hudu_asset_name`, `hudu_company_id`, `asset_layout_id`,
|
||
`asset_layout_name`, `primary_serial`, `url`. One-to-one both directions
|
||
per tenant (existing unique indexes are scoped by `alga_entity_type`).
|
||
- FR2. Auto-suggest matches per Hudu asset against the client's Alga assets:
|
||
serial exact (confidence 1.0) → name exact (0.9) → name fuzzy ≥0.8
|
||
(reusing the Phase 1 normalized-Levenshtein matcher incl. suffix rules);
|
||
one-to-one greedy claiming, mapped rows/assets excluded.
|
||
- FR3. Mapping UI follows the staged-changes pattern: picker stages, Save
|
||
commits (clear+set for replace), Discard reverts, suggestions are
|
||
confirmed by Save and dismissible per row.
|
||
|
||
Assets — import:
|
||
- FR4. Import creates an Alga asset via the existing `createAsset` action:
|
||
`client_id` = the mapped client, `name` = Hudu asset name,
|
||
`serial_number` = `primary_serial` (when present), `asset_type` = layout
|
||
map lookup (fallback `unknown`), `asset_tag` = `primary_serial` if unique
|
||
else `hudu-<hudu asset id>`, `status` = the tenant's default/first asset
|
||
status (same default the manual create form uses). A mapping row is
|
||
created atomically with the asset.
|
||
- FR5. "Import all unmatched" imports every unmapped, unsuggested Hudu asset
|
||
for the client sequentially, reporting created/failed counts; individual
|
||
failures don't abort the batch.
|
||
- FR6. Import requires the `asset` RBAC create permission (in addition to the
|
||
Phase 1 client-tab gate); the UI hides Import affordances without it.
|
||
|
||
Assets — sync:
|
||
- FR7. "Sync from Hudu" re-fetches the company's Hudu assets and, for each
|
||
mapped pair, updates the Alga asset's synced fields — `name`,
|
||
`serial_number` — when they differ (Hudu wins on synced fields only;
|
||
other Alga fields untouched). `asset_type` is not retro-changed.
|
||
- FR8. Sync flags mappings whose Hudu asset is archived/absent as stale
|
||
(metadata `stale: true` + UI badge); it never deletes Alga assets or
|
||
mappings. Unflagging happens automatically if the asset reappears.
|
||
- FR9. Sync records `last_synced_at` on affected mapping rows and surfaces a
|
||
result summary (updated / unchanged / stale counts) in the UI.
|
||
- FR10. Sync requires the `asset` RBAC update permission.
|
||
|
||
Layout→type map:
|
||
- FR11. `hudu_integrations.settings.asset_layout_type_map` (jsonb:
|
||
`{ "<layout_id>": "<alga asset_type>" }`) with server action get/set
|
||
(system_settings update RBAC) and live layout listing via
|
||
`GET /api/v1/asset_layouts`.
|
||
- FR12. Heuristic prefill computes suggested types for unconfigured layouts
|
||
(UI-side); unconfigured layouts import as `unknown`.
|
||
|
||
Documents — client section:
|
||
- FR13. Client Documents tab renders a link-only "Hudu Documentation"
|
||
section (name, updated_at, deep-link) using the Phase 1 per-company
|
||
articles fetch/cache; same gate as the client Hudu tab; collapsed by
|
||
default; independent error/empty states that never break the native
|
||
documents UI.
|
||
|
||
Documents — global tab:
|
||
- FR14. A server action lists Hudu articles across companies (no
|
||
`company_id` filter), one Hudu page per request (25 items), passing the
|
||
user's search term to Hudu, and resolves each article's `company_id` to
|
||
the mapped Alga client via the companies cache + mapping rows.
|
||
- FR15. Documents page "Hudu" tab renders the paged list with search,
|
||
resolved client names ("Unmapped" badge otherwise), and deep-links.
|
||
Visible only when EE + flag + connected; CE/flag-off tenants see no trace.
|
||
- FR16. All new server surfaces enforce the Phase 1 guard chain (EE add-on +
|
||
tier + flag + RBAC) server-side, not just in the UI.
|
||
|
||
## Non-functional Requirements
|
||
|
||
- NFR1. Pull-only; no Hudu writes anywhere.
|
||
- NFR2. Respect Hudu limits: never fan out unbounded page fetches; bulk
|
||
import/sync fetch the company's assets with bounded pagination and stop on
|
||
rate-limit (429) with a typed, user-visible error.
|
||
- NFR3. Article content is never persisted; only ids/names/timestamps already
|
||
cached for lists.
|
||
- NFR4. Mapping rows remain direction-agnostic (no pull-only columns).
|
||
- NFR5. New UI strings fully translated (8 locales) and pass the Phase 1
|
||
i18n static checks (extended to new components).
|
||
|
||
## Data / Integration Notes
|
||
|
||
- No new tables. Asset mappings reuse `tenant_external_entity_mappings`
|
||
(indexes verified: uniqueness is per `alga_entity_type`, so client and
|
||
asset mappings coexist). Layout map lives in `hudu_integrations.settings`.
|
||
- New `HuduClient` surfaces: `GET /api/v1/asset_layouts`, global
|
||
`GET /api/v1/articles?page=&search=` (verify Hudu's article search param
|
||
name during implementation; fall back to `?name=` filter if needed).
|
||
- Alga asset creation: `createAsset` in `packages/assets/src/actions/
|
||
assetActions.ts` (`CreateAssetRequest`: fixed `asset_type` enum, required
|
||
`asset_tag`/`status`).
|
||
- Client Documents tab host: `ClientDetails.tsx` tab id `documents`;
|
||
EE injection follows the Phase 1 `useHuduClientTab` precedent.
|
||
- Documents page: `packages/documents/src/components/DocumentsPage.tsx`.
|
||
|
||
## Risks
|
||
|
||
- R1. Hudu asset layouts are arbitrary per instance; the heuristic prefill
|
||
will misclassify exotic layouts → mitigated by explicit admin map +
|
||
`unknown` fallback.
|
||
- R2. `asset_tag` uniqueness semantics in Alga need verification before
|
||
using `primary_serial` as tag (fallback `hudu-<id>` is always unique).
|
||
- R3. Cross-client article listing's client resolution depends on the
|
||
companies cache freshness; stale cache shows "Unmapped" — acceptable,
|
||
refreshable.
|
||
- R4. DocumentsPage is a CE package surface; the EE tab must be injected
|
||
without breaking CE builds (follow the ClientDetails gate-hook pattern).
|
||
|
||
## Open Questions
|
||
|
||
- OQ1. Hudu global article search: exact query param (`search` vs `name`)
|
||
and whether it spans body or title only — verify against the local
|
||
instance during implementation.
|
||
- OQ2. Asset status default on import: confirm the tenant's default status
|
||
source used by the manual create form and reuse it.
|
||
|
||
## Acceptance Criteria
|
||
|
||
- AC1. A technician can map, import (single + bulk), and sync a client's
|
||
Hudu assets entirely from the client Hudu tab, with explicit Save
|
||
confirmation and no destructive surprises (stale ≠ deleted).
|
||
- AC2. An imported asset appears in Alga's asset module with correct client,
|
||
name, serial, and type per the layout map, and is immediately mapped.
|
||
- AC3. Sync updates renamed/re-serialed Hudu assets' mapped Alga twins and
|
||
flags disappeared ones, reporting counts.
|
||
- AC4. The client Documents tab shows the mapped company's Hudu articles and
|
||
deep-links correctly; unmapped/disconnected clients show nothing extra.
|
||
- AC5. The Documents page Hudu tab lists and searches articles across
|
||
companies with correct client resolution and pagination; invisible on CE
|
||
or with the flag off.
|
||
- AC6. All new strings translated in 8 locales; i18n static checks extended
|
||
and green; full Hudu unit suite green.
|