docs: add project_phases table, fix tasks.project_id resolution
- Add project_phases section: schema, purpose, why it exists - Document the tasks.project_id backfill mechanism (Autotask Tasks API omits projectID; resolved post-sync via phase_id join) - Add query for tasks whose parent project is archived (project_id NULL but resolvable via project_phases) - Update domain table to include project_phases (~2.7K rows)
This commit is contained in:
parent
4d75a099d7
commit
b25557a310
1 changed files with 39 additions and 2 deletions
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
| Domain | Key Tables | Approx Rows | Description |
|
| Domain | Key Tables | Approx Rows | Description |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| **Autotask PSA** | tickets, time_entries, companies, contacts, resources, configuration_items, contracts, projects, tasks, ticket_notes | 152K tickets, 156K time entries, 7K CIs | Service desk, billing, contracts, clients |
|
| **Autotask PSA** | tickets, time_entries, companies, contacts, resources, configuration_items, contracts, projects, project_phases, tasks, ticket_notes | 152K tickets, 156K time entries, 7K CIs, 2.7K phases | Service desk, billing, contracts, clients |
|
||||||
| **Datto RMM** | datto_rmm_alerts, datto_rmm_devices, datto_rmm_sites | 21K alerts, 3.6K devices | Remote monitoring & management |
|
| **Datto RMM** | datto_rmm_alerts, datto_rmm_devices, datto_rmm_sites | 21K alerts, 3.6K devices | Remote monitoring & management |
|
||||||
| **Auvik** | auvik_tenants, auvik_tenant_mappings | 17 tenants, 15 mappings | Network management — device inventory fetched live from API; tenant/company links stored in DB |
|
| **Auvik** | auvik_tenants, auvik_tenant_mappings | 17 tenants, 15 mappings | Network management — device inventory fetched live from API; tenant/company links stored in DB |
|
||||||
| **SentinelOne** | s1_agents, s1_threats, s1_sites | 2.8K agents, 4.1K threats | Endpoint security |
|
| **SentinelOne** | s1_agents, s1_threats, s1_sites | 2.8K agents, 4.1K threats | Endpoint security |
|
||||||
|
|
@ -222,6 +222,35 @@ Planned engagements for client work — distinct from reactive service tickets.
|
||||||
|
|
||||||
**Relationship to tickets:** A ticket can optionally be linked to a project via `tickets.project_id`. This is typically used when a ticket was generated as part of project work (e.g. a change request within a project).
|
**Relationship to tickets:** A ticket can optionally be linked to a project via `tickets.project_id`. This is typically used when a ticket was generated as part of project work (e.g. a change request within a project).
|
||||||
|
|
||||||
|
### project_phases (~2.7K rows, 19 columns)
|
||||||
|
|
||||||
|
Phases group tasks within a project (e.g. "Equipment Selection", "Installation", "Testing"). Every phase belongs to exactly one project.
|
||||||
|
|
||||||
|
**Key columns:**
|
||||||
|
- `id` (bigint PK) — Autotask phase ID (shares namespace with tasks)
|
||||||
|
- `project_id` (bigint) — FK to `projects.id` (no FK constraint — may reference archived projects)
|
||||||
|
- `parent_phase_id` (bigint) — parent phase for nested phase trees
|
||||||
|
- `title` (varchar) — phase name
|
||||||
|
- `description` (text)
|
||||||
|
- `phase_number` (varchar) — e.g. `T20251222.0116`
|
||||||
|
- `estimated_hours` (numeric)
|
||||||
|
- `start_date_time`, `due_date` (timestamp) — scheduled window
|
||||||
|
- `create_date_time`, `last_activity_date_time` (timestamp)
|
||||||
|
- `is_scheduled` (boolean)
|
||||||
|
- `creator_resource_id` (bigint) — FK to `resources.id`
|
||||||
|
- `is_deleted` (boolean)
|
||||||
|
|
||||||
|
**Why this table matters:** The Autotask Tasks bulk API omits `projectID` from its response. After each `project_phases` sync, an automatic backfill updates `tasks.project_id` by joining `tasks.phase_id = project_phases.id`. This is the only way to resolve which project a task belongs to.
|
||||||
|
|
||||||
|
**Join to get a task's project via phase:**
|
||||||
|
```sql
|
||||||
|
SELECT t.id, t.title, pp.title AS phase, p.project_name
|
||||||
|
FROM tasks t
|
||||||
|
JOIN project_phases pp ON pp.id = t.phase_id
|
||||||
|
LEFT JOIN projects p ON p.id = pp.project_id
|
||||||
|
WHERE t.is_deleted IS NOT TRUE;
|
||||||
|
```
|
||||||
|
|
||||||
### tasks (~5K rows, 34 columns)
|
### tasks (~5K rows, 34 columns)
|
||||||
|
|
||||||
Tasks are discrete units of work that live **inside** a project or a ticket. They are NOT standalone items — every task belongs to either a project (`project_id`) or a ticket (`ticket_id`), not both simultaneously.
|
Tasks are discrete units of work that live **inside** a project or a ticket. They are NOT standalone items — every task belongs to either a project (`project_id`) or a ticket (`ticket_id`), not both simultaneously.
|
||||||
|
|
@ -254,13 +283,21 @@ Tasks are discrete units of work that live **inside** a project or a ticket. The
|
||||||
|
|
||||||
**Time entries on tasks:** `time_entries.task_id` links work logs to specific tasks. Task time entries also carry `project_id` and `company_id` for quick aggregation without joins.
|
**Time entries on tasks:** `time_entries.task_id` links work logs to specific tasks. Task time entries also carry `project_id` and `company_id` for quick aggregation without joins.
|
||||||
|
|
||||||
|
**Known sync behaviour — `project_id` population:**
|
||||||
|
The Autotask bulk Tasks API does not return `projectID` in its response. `project_id` is instead populated post-sync via a backfill that joins `tasks.phase_id → project_phases.id → project_phases.project_id`. This backfill runs automatically every time `project_phases` is synced. Tasks whose parent project is completed/archived (and therefore absent from our `projects` table) will still have `project_id = NULL` — use `phase_id → project_phases.project_id` directly in those cases.
|
||||||
|
|
||||||
**Finding project tasks vs ticket tasks:**
|
**Finding project tasks vs ticket tasks:**
|
||||||
```sql
|
```sql
|
||||||
-- Project tasks only
|
-- Project tasks only (project_id populated via phase backfill)
|
||||||
SELECT t.*, p.project_name FROM tasks t
|
SELECT t.*, p.project_name FROM tasks t
|
||||||
JOIN projects p ON p.id = t.project_id
|
JOIN projects p ON p.id = t.project_id
|
||||||
WHERE t.project_id IS NOT NULL AND t.is_deleted IS NOT TRUE;
|
WHERE t.project_id IS NOT NULL AND t.is_deleted IS NOT TRUE;
|
||||||
|
|
||||||
|
-- Tasks whose project is archived (project_id NULL but phase_id resolvable)
|
||||||
|
SELECT t.*, pp.project_id AS phase_project_id, pp.title AS phase_title
|
||||||
|
FROM tasks t JOIN project_phases pp ON pp.id = t.phase_id
|
||||||
|
WHERE t.project_id IS NULL AND t.is_deleted IS NOT TRUE;
|
||||||
|
|
||||||
-- Ticket sub-tasks only
|
-- Ticket sub-tasks only
|
||||||
SELECT t.*, tk.title AS ticket_title FROM tasks t
|
SELECT t.*, tk.title AS ticket_title FROM tasks t
|
||||||
JOIN tickets tk ON tk.id = t.ticket_id
|
JOIN tickets tk ON tk.id = t.ticket_id
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue