feat(tasks): Task tracking, IP-per-store, and schedule edit fixes

- Add task completion verification with DB and output layers
- Add reconciliation loop to sync worker memory with DB state
- Implement IP-per-store-per-platform conflict detection
- Add task ID hash to MinIO payload filenames for traceability
- Fix schedule edit modal with dispensary info in API responses
- Add task ID display after dispensary name in worker dashboard
- Add migrations for proxy_ip and source tracking columns

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Kelly
2025-12-14 10:49:21 -07:00
parent 3e9667571f
commit 9518ca48a5
12 changed files with 546 additions and 29 deletions

View File

@@ -225,23 +225,27 @@ router.get('/schedules', async (req: Request, res: Response) => {
query = `
SELECT ts.id, ts.name, ts.role, ts.description, ts.enabled, ts.interval_hours,
ts.priority, ts.state_code, ts.pool_id, tp.display_name as pool_name,
ts.dispensary_id, d.name as dispensary_name,
ts.platform, ts.method,
COALESCE(ts.is_immutable, false) as is_immutable,
ts.last_run_at, ts.next_run_at,
ts.last_task_count, ts.last_error, ts.created_at, ts.updated_at
FROM task_schedules ts
LEFT JOIN task_pools tp ON tp.id = ts.pool_id
LEFT JOIN dispensaries d ON d.id = ts.dispensary_id
`;
} else {
// Fallback query without pool_id (migration 114 not yet run)
query = `
SELECT ts.id, ts.name, ts.role, ts.description, ts.enabled, ts.interval_hours,
ts.priority, ts.state_code, NULL::integer as pool_id, NULL::text as pool_name,
ts.dispensary_id, d.name as dispensary_name,
ts.platform, ts.method,
COALESCE(ts.is_immutable, false) as is_immutable,
ts.last_run_at, ts.next_run_at,
ts.last_task_count, ts.last_error, ts.created_at, ts.updated_at
FROM task_schedules ts
LEFT JOIN dispensaries d ON d.id = ts.dispensary_id
`;
}
@@ -534,13 +538,23 @@ router.put('/schedules/:id', async (req: Request, res: Response) => {
SET ${updates.join(', ')}
WHERE id = $${paramIndex}
RETURNING id, name, role, description, enabled, interval_hours,
priority, state_code, platform, method,
priority, state_code, platform, method, dispensary_id, pool_id,
COALESCE(is_immutable, false) as is_immutable,
last_run_at, next_run_at,
last_task_count, last_error, created_at, updated_at
`, values);
res.json(result.rows[0]);
// Add dispensary_name if dispensary_id is set
const updatedSchedule = result.rows[0];
if (updatedSchedule.dispensary_id) {
const dispResult = await pool.query(
'SELECT name FROM dispensaries WHERE id = $1',
[updatedSchedule.dispensary_id]
);
updatedSchedule.dispensary_name = dispResult.rows[0]?.name || null;
}
res.json(updatedSchedule);
} catch (error: any) {
if (error.code === '23505') {
return res.status(409).json({ error: 'A schedule with this name already exists' });