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:
@@ -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' });
|
||||
|
||||
Reference in New Issue
Block a user