feat(projects): gated project pickers on quote/contract/hours editors
- ProjectSelect: a reusable picker that renders nothing when the projects flag is off (satisfies 'book to project hidden unless projects enabled'). - projects.service.ts: full frontend API client (list/get/create/update, overview, assign event/quote/contract, email preview + 4 actions). - Quote + contract editors carry an optional projectId (state, prefill, payload); service payload/detail types updated. - HoursSection gains a 'book to project' control; backend createEntry persists project_id (migration 118, hasColumnCached guarded).
This commit is contained in:
@@ -572,6 +572,7 @@ router.post('/:id/hour-entries', [
|
||||
body('endTime').matches(/^([01]\d|2[0-3]):[0-5]\d$/),
|
||||
body('hourlyRateMinorOverride').optional({ nullable: true }).isInt({ min: 0 }),
|
||||
body('description').optional({ nullable: true }).isString().isLength({ max: 1000 }),
|
||||
body('projectId').optional({ nullable: true }).isInt({ min: 1 }),
|
||||
], handleAsync(async (req, res) => {
|
||||
validateRequest(req);
|
||||
const result = await customerHoursService.createEntry(
|
||||
|
||||
@@ -233,6 +233,10 @@ async function createEntry(customerId, payload, adminId) {
|
||||
created_at: new Date(),
|
||||
updated_at: new Date(),
|
||||
};
|
||||
// Migration 118 — optional "book to project" link.
|
||||
if (payload.projectId !== undefined && await hasColumnCached('customer_hour_entries', 'project_id')) {
|
||||
row.project_id = payload.projectId || null;
|
||||
}
|
||||
const inserted = await trx('customer_hour_entries').insert(row).returning('id');
|
||||
const entryId = typeof inserted[0] === 'object' ? inserted[0].id : inserted[0];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user