feat: Support per-dispensary schedules (not just per-state)
- Add dispensary_id column to task_schedules table - Update scheduler to handle single-dispensary schedules - Update run-now endpoint to handle single-dispensary schedules - Update frontend modal to pass dispensary_id when 1 store selected - Fix existing "Deeply Rooted Hourly" schedule with dispensary_id=112 Now when you select ONE store and check "Make recurring", it creates a schedule that runs for that specific store every interval. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -286,6 +286,7 @@ router.post('/schedules', async (req: Request, res: Response) => {
|
||||
interval_hours,
|
||||
priority = 0,
|
||||
state_code,
|
||||
dispensary_id,
|
||||
platform,
|
||||
} = req.body;
|
||||
|
||||
@@ -300,12 +301,12 @@ router.post('/schedules', async (req: Request, res: Response) => {
|
||||
|
||||
const result = await pool.query(`
|
||||
INSERT INTO task_schedules
|
||||
(name, role, description, enabled, interval_hours, priority, state_code, platform, next_run_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
(name, role, description, enabled, interval_hours, priority, state_code, dispensary_id, platform, next_run_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
RETURNING id, name, role, description, enabled, interval_hours,
|
||||
priority, state_code, platform, last_run_at, next_run_at,
|
||||
priority, state_code, dispensary_id, platform, last_run_at, next_run_at,
|
||||
last_task_count, last_error, created_at, updated_at
|
||||
`, [name, role, description, enabled, interval_hours, priority, state_code, platform, nextRunAt]);
|
||||
`, [name, role, description, enabled, interval_hours, priority, state_code, dispensary_id, platform, nextRunAt]);
|
||||
|
||||
res.status(201).json(result.rows[0]);
|
||||
} catch (error: any) {
|
||||
@@ -536,7 +537,7 @@ router.post('/schedules/:id/run-now', async (req: Request, res: Response) => {
|
||||
|
||||
// Get the full schedule
|
||||
const scheduleResult = await pool.query(`
|
||||
SELECT id, name, role, state_code, platform, priority, interval_hours, method
|
||||
SELECT id, name, role, state_code, dispensary_id, platform, priority, interval_hours, method
|
||||
FROM task_schedules WHERE id = $1
|
||||
`, [scheduleId]);
|
||||
|
||||
@@ -547,9 +548,45 @@ router.post('/schedules/:id/run-now', async (req: Request, res: Response) => {
|
||||
const schedule = scheduleResult.rows[0];
|
||||
let tasksCreated = 0;
|
||||
|
||||
// For product crawl roles with state_code, fan out to individual stores
|
||||
const isCrawlRole = ['product_discovery', 'product_refresh', 'payload_fetch'].includes(schedule.role);
|
||||
if (isCrawlRole && schedule.state_code) {
|
||||
|
||||
// Single-dispensary schedule (e.g., "Deeply Rooted Hourly")
|
||||
if (isCrawlRole && schedule.dispensary_id) {
|
||||
// Check if this specific store can be refreshed (no pending task)
|
||||
const storeResult = await pool.query(`
|
||||
SELECT d.id, d.name
|
||||
FROM dispensaries d
|
||||
WHERE d.id = $1
|
||||
AND d.crawl_enabled = true
|
||||
AND d.platform_dispensary_id IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM worker_tasks t
|
||||
WHERE t.dispensary_id = d.id
|
||||
AND t.role IN ('product_discovery', 'product_refresh', 'payload_fetch')
|
||||
AND t.status IN ('pending', 'claimed', 'running')
|
||||
)
|
||||
`, [schedule.dispensary_id]);
|
||||
|
||||
if (storeResult.rows.length > 0) {
|
||||
await taskService.createTask({
|
||||
role: 'product_discovery',
|
||||
dispensary_id: schedule.dispensary_id,
|
||||
platform: schedule.platform || 'dutchie',
|
||||
priority: schedule.priority + 10,
|
||||
method: schedule.method || 'http',
|
||||
});
|
||||
tasksCreated = 1;
|
||||
} else {
|
||||
return res.json({
|
||||
success: true,
|
||||
message: `Store ${schedule.dispensary_id} has a pending task or is disabled`,
|
||||
tasksCreated: 0,
|
||||
dispensaryId: schedule.dispensary_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
// Per-state schedule (e.g., "AZ Product Refresh")
|
||||
else if (isCrawlRole && schedule.state_code) {
|
||||
// Find stores in this state needing refresh
|
||||
const storeResult = await pool.query(`
|
||||
SELECT d.id
|
||||
@@ -599,9 +636,9 @@ router.post('/schedules/:id/run-now', async (req: Request, res: Response) => {
|
||||
});
|
||||
tasksCreated = 1;
|
||||
} else {
|
||||
// Crawl role without state_code - shouldn't happen, reject
|
||||
// Crawl role without dispensary_id or state_code - reject
|
||||
return res.status(400).json({
|
||||
error: `${schedule.role} schedules require a state_code`,
|
||||
error: `${schedule.role} schedules require a dispensary_id or state_code`,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user