| 22 | | Represents projects owned by users. |
| 23 | | |
| 24 | | ''Candidate keys:'' |
| 25 | | * project_id (primary key) |
| 26 | | |
| 27 | | ''Attributes:'' |
| 28 | | * project_id – numeric |
| 29 | | * client_id – numeric |
| 30 | | * name – text |
| 31 | | * status – text |
| 32 | | * startDate – date |
| 33 | | * endDate – date |
| 34 | | |
| 35 | | === Relationships === |
| 36 | | * User owns Project (1:N) |
| 37 | | * Project includes Task (1:N) |
| 38 | | * Task has File (1:N) |
| 39 | | * Task has Comment (1:N) |
| 40 | | * TimesheetProject contains TimesheetEntry (1:N) |
| 41 | | |
| 42 | | == Entity-Relationship Model History == |
| 43 | | * v01 – Initial ER model created based on project requirements. |
| | 26 | A deliverable engagement carried out for a client. The service_type plus a flexible metadata (JSON) field allow one entity to describe several kinds of work without a separate table per type. |
| | 27 | ''Candidate keys:'' project_id (primary key). |
| | 28 | ''Attributes:'' |
| | 29 | * project_id – numeric, required, primary key |
| | 30 | * name – text, required |
| | 31 | * description – text, optional |
| | 32 | * client_id – numeric, optional, foreign key → Users |
| | 33 | * status – text, required (default ACTIVE) |
| | 34 | * service_type – text, required (default OTHER) |
| | 35 | * metadata – json, optional |
| | 36 | * start_date – date, optional |
| | 37 | * end_date – date, optional |
| | 38 | * created_at – date/time, required |
| | 39 | * updated_at – date/time, required |
| | 40 | |
| | 41 | === Domains === |
| | 42 | A web domain owned by a client, tracking hosting details and a renewal lifecycle. Cascade-deletes with the client. |
| | 43 | ''Candidate keys:'' domain_id (primary key). |
| | 44 | ''Attributes:'' |
| | 45 | * domain_id – numeric, required, primary key |
| | 46 | * client_id – numeric, required, foreign key → Users |
| | 47 | * domain_name – text, required |
| | 48 | * is_primary – boolean, required (default false) |
| | 49 | * is_active – boolean, required (default true) |
| | 50 | * hosting_provider – text, optional |
| | 51 | * hosting_expiry – date/time, optional |
| | 52 | * expiration_date – date/time, optional |
| | 53 | * status – text, required (default PENDING) |
| | 54 | * notes – text, optional |
| | 55 | |
| | 56 | === Tasks === |
| | 57 | A unit of work carried out for a client, optionally grouped under a project. Always tied to a client; the project link is optional so ad-hoc tasks can exist. |
| | 58 | ''Candidate keys:'' task_id (primary key). |
| | 59 | ''Attributes:'' |
| | 60 | * task_id – numeric, required, primary key |
| | 61 | * title – text, required |
| | 62 | * description – text, optional |
| | 63 | * status – text, required (default PENDING) |
| | 64 | * due_date – date/time, optional |
| | 65 | * client_id – numeric, required, foreign key → Users |
| | 66 | * project_id – numeric, optional, foreign key → Projects |
| | 67 | * created_at – date/time, required |
| | 68 | * updated_at – date/time, required |
| | 69 | |
| | 70 | === Milestones === |
| | 71 | An ordered sub-step of a task, tracking completion and deployment state. Cascade-deletes with its task. |
| | 72 | ''Candidate keys:'' milestone_id (primary key). |
| | 73 | ''Attributes:'' |
| | 74 | * milestone_id – numeric, required, primary key |
| | 75 | * task_id – numeric, required, foreign key → Tasks |
| | 76 | * title – text, required |
| | 77 | * description – text, optional |
| | 78 | * is_done – boolean, required (default false) |
| | 79 | * deployed – boolean, required (default false) |
| | 80 | * order – numeric, required (default 0) |
| | 81 | * created_at – date/time, required |
| | 82 | |
| | 83 | === TaskWorkers === |
| | 84 | Associative (junction) entity resolving the many-to-many between tasks and users (workers). The unique (task_id, user_id) pair prevents duplicate assignments. |
| | 85 | ''Candidate keys:'' id (primary key); composite (task_id, user_id) as unique alternate key. |
| | 86 | ''Attributes:'' |
| | 87 | * id – numeric, required, primary key |
| | 88 | * task_id – numeric, required, foreign key → Tasks |
| | 89 | * user_id – numeric, required, foreign key → Users |
| | 90 | |
| | 91 | === TaskFiles === |
| | 92 | A file or deliverable attached to a task, with an approval workflow, versioning, and a client-visibility flag. Cascade-deletes with its task. |
| | 93 | ''Candidate keys:'' file_id (primary key). |
| | 94 | ''Attributes:'' |
| | 95 | * file_id – numeric, required, primary key |
| | 96 | * task_id – numeric, required, foreign key → Tasks |
| | 97 | * file_name – text, required |
| | 98 | * file_url – text, required |
| | 99 | * file_type – text, required |
| | 100 | * version – numeric, required (default 1) |
| | 101 | * review_status – enum (PENDING | APPROVED | NEEDS_REVISION | REJECTED) |
| | 102 | * visible_to_client – boolean, required (default true) |
| | 103 | * uploaded_at – date/time, required |
| | 104 | |
| | 105 | === FileComments === |
| | 106 | A comment on a task file, forming a per-file discussion thread. Cascades with the file. |
| | 107 | ''Candidate keys:'' file_comment_id (primary key). |
| | 108 | ''Attributes:'' |
| | 109 | * file_comment_id – numeric, required, primary key |
| | 110 | * file_id – numeric, required, foreign key → TaskFiles |
| | 111 | * user_id – numeric, required, foreign key → Users |
| | 112 | * content – text, required |
| | 113 | * visible_to_client – boolean, required (default false) |
| | 114 | * created_at – date/time, required |
| | 115 | |
| | 116 | === TaskComments === |
| | 117 | A comment on a task, forming a per-task discussion thread. Cascades with the task. |
| | 118 | ''Candidate keys:'' task_comment_id (primary key). |
| | 119 | ''Attributes:'' |
| | 120 | * task_comment_id – numeric, required, primary key |
| | 121 | * task_id – numeric, required, foreign key → Tasks |
| | 122 | * user_id – numeric, required, foreign key → Users |
| | 123 | * content – text, required |
| | 124 | * visible_to_client – boolean, required (default false) |
| | 125 | * created_at – date/time, required |
| | 126 | |
| | 127 | === Invoices === |
| | 128 | A bill issued to a client. Acts as the header for its line items and payments, storing monetary summaries. |
| | 129 | ''Candidate keys:'' invoice_id (primary key); invoice_number (unique alternate key). |
| | 130 | ''Attributes:'' |
| | 131 | * invoice_id – numeric, required, primary key |
| | 132 | * invoice_number – text, required, unique |
| | 133 | * amount – decimal, required |
| | 134 | * status – text, required (default PENDING) |
| | 135 | * due_date – date/time, required |
| | 136 | * issue_date – date/time, optional |
| | 137 | * subtotal – decimal, optional |
| | 138 | * tax_amount – decimal, optional |
| | 139 | * client_id – numeric, required, foreign key → Users |
| | 140 | * created_at – date/time, required |
| | 141 | |
| | 142 | === Payments === |
| | 143 | A single payment recorded against an invoice. Modeled separately (rather than a paid flag) to support partial and multiple payments. Cascades with the invoice. |
| | 144 | ''Candidate keys:'' payment_id (primary key). |
| | 145 | ''Attributes:'' |
| | 146 | * payment_id – numeric, required, primary key |
| | 147 | * invoice_id – numeric, required, foreign key → Invoices |
| | 148 | * amount – decimal, required |
| | 149 | * paid_at – date/time, required |
| | 150 | * note – text, optional |
| | 151 | |
| | 152 | === InvoiceLineItems === |
| | 153 | One billed line on an invoice. Cascades with the invoice. |
| | 154 | ''Candidate keys:'' line_item_id (primary key). |
| | 155 | ''Attributes:'' |
| | 156 | * line_item_id – numeric, required, primary key |
| | 157 | * invoice_id – numeric, required, foreign key → Invoices |
| | 158 | * name – text, required |
| | 159 | * quantity – decimal, required |
| | 160 | * unit_price – decimal, required |
| | 161 | * sort_order – numeric, required (default 0) |
| | 162 | |
| | 163 | === TimesheetProjects === |
| | 164 | A project bucket used purely for time tracking, separate from delivery projects so hours can be logged against informal or internal work. |
| | 165 | ''Candidate keys:'' ts_project_id (primary key). |
| | 166 | ''Attributes:'' |
| | 167 | * ts_project_id – numeric, required, primary key |
| | 168 | * project_name – text, required |
| | 169 | * client_id – numeric, optional, foreign key → Users |
| | 170 | * description – text, optional |
| | 171 | * is_paid – boolean, required (default false) |
| | 172 | * created_at – date/time, required |
| | 173 | * updated_at – date/time, required |
| | 174 | |
| | 175 | === TimesheetEntries === |
| | 176 | A logged block of hours under a timesheet project. Cascades with its timesheet project. |
| | 177 | ''Candidate keys:'' ts_entry_id (primary key). |
| | 178 | ''Attributes:'' |
| | 179 | * ts_entry_id – numeric, required, primary key |
| | 180 | * project_id – numeric, required, foreign key → TimesheetProjects |
| | 181 | * date – date/time, required |
| | 182 | * hours_worked – decimal, required |
| | 183 | * hourly_rate – decimal, required |
| | 184 | * total_pay – decimal, required |
| | 185 | * notes – text, optional |
| | 186 | |
| | 187 | === Notifications === |
| | 188 | An in-app notification delivered to a user, with type/source metadata used to route and badge messages. Cascades with the user. |
| | 189 | ''Candidate keys:'' notification_id (primary key). |
| | 190 | ''Attributes:'' |
| | 191 | * notification_id – numeric, required, primary key |
| | 192 | * user_id – numeric, required, foreign key → Users |
| | 193 | * type – text, required |
| | 194 | * title – text, required |
| | 195 | * message – text, required |
| | 196 | * read – boolean, required (default false) |
| | 197 | * created_at – date/time, required |
| | 198 | |
| | 199 | === Invites === |
| | 200 | A pending invitation to onboard a new user. The token is stored hashed for security, along with prefilled onboarding data and an expiry. |
| | 201 | ''Candida |