feat(projects): link quotes & contracts to a project (precise cockpit rollup)

- Migration 121 adds quotes.project_id + contracts.project_id (nullable FK,
  index) and backfills the unambiguous single-project-per-customer case.
- projectService rolls quotes/contracts up by project_id, with a
  customer-based fallback on pre-121 DBs (hasColumnCached guarded).
- quote/contract create+update accept an optional projectId; detail
  transforms surface it for editor prefill.
- POST /projects/:id/quotes and /:id/contracts assign endpoints.
This commit is contained in:
Luca
2026-06-06 13:05:36 +02:00
parent 1bf0b34ea5
commit 6420047e7c
7 changed files with 152 additions and 14 deletions
+8
View File
@@ -803,6 +803,10 @@ async function createContract(payload, adminId) {
row.event_time_start = payload.eventTimeStart || null;
row.event_time_end = payload.eventTimeEnd || null;
}
// Migration 121 — optional link to a Project Overview project.
if (payload.projectId !== undefined && await hasColumnCached('contracts', 'project_id')) {
row.project_id = payload.projectId || null;
}
const inserted = await trx('contracts').insert(row).returning('id');
const contractId = typeof inserted[0] === 'object' ? inserted[0].id : inserted[0];
@@ -890,6 +894,10 @@ async function updateContract(id, payload, adminId) {
for (const [api, col] of Object.entries(map)) {
if (api in payload) updates[col] = payload[api] || null;
}
// Migration 121 — optional Project Overview link.
if ('projectId' in payload && await hasColumnCached('contracts', 'project_id')) {
updates.project_id = payload.projectId || null;
}
await trx('contracts').where({ id }).update(updates);
// Replace inclusions only when the caller sent an explicit list.