Files
cannaiq/backend/migrations/022_scheduler_dispensary_link_fields.sql
Kelly 5306c3f4ca Switch scheduler UI to dispensary-based API
- Add migrations 021-023 for dispensary_crawl_schedule tables and views
- Add dispensary-orchestrator service and bootstrap-discovery script
- Update schedule routes with dispensary endpoints (/api/schedule/dispensaries)
- Switch frontend scheduler to use canonical dispensaries table (182 AZDHS entries)
- Add dispensary schedule API methods to frontend api.ts
- Remove "Unmapped" badge logic - all dispensaries now linked properly
- Add proper URL linking to dispensary detail pages (/dispensaries/:state/:city/:slug)
- Update Jobs table to show dispensary_name

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 18:54:52 -07:00

78 lines
2.5 KiB
SQL

-- =====================================================
-- Add dispensary slug/state to crawl_schedule_status view
-- =====================================================
-- Enables proper linking from scheduler to dispensary detail page
-- which uses route /dispensaries/:state/:city/:slug
DROP VIEW IF EXISTS crawl_schedule_status;
CREATE OR REPLACE VIEW crawl_schedule_status AS
SELECT
s.id AS store_id,
s.name AS store_name,
s.slug AS store_slug,
s.timezone,
s.active,
s.scrape_enabled,
s.last_scraped_at,
-- Dispensary info (master record)
s.dispensary_id,
d.name AS dispensary_name,
d.company_name AS dispensary_company,
d.city AS dispensary_city,
d.state AS dispensary_state,
d.slug AS dispensary_slug,
d.address AS dispensary_address,
d.menu_url AS dispensary_menu_url,
-- Provider intelligence from dispensary (if linked)
d.product_provider,
d.product_confidence,
d.product_crawler_mode,
-- Schedule settings (use store override or global)
COALESCE(scs.enabled, TRUE) AS schedule_enabled,
COALESCE(scs.interval_hours, cs_global.interval_hours, 4) AS interval_hours,
COALESCE(scs.daily_special_enabled, TRUE) AS daily_special_enabled,
COALESCE(scs.daily_special_time, '00:01'::TIME) AS daily_special_time,
COALESCE(scs.priority, 0) AS priority,
-- Orchestrator status
scs.last_status,
scs.last_summary,
scs.last_run_at AS schedule_last_run,
scs.last_error,
-- Next scheduled run calculation
CASE
WHEN s.last_scraped_at IS NULL THEN NOW()
ELSE s.last_scraped_at + (COALESCE(scs.interval_hours, cs_global.interval_hours, 4) || ' hours')::INTERVAL
END AS next_scheduled_run,
-- Latest job info
cj.id AS latest_job_id,
cj.status AS latest_job_status,
cj.job_type AS latest_job_type,
cj.trigger_type AS latest_job_trigger,
cj.started_at AS latest_job_started,
cj.completed_at AS latest_job_completed,
cj.products_found AS latest_products_found,
cj.products_new AS latest_products_new,
cj.products_updated AS latest_products_updated,
cj.error_message AS latest_job_error
FROM stores s
LEFT JOIN dispensaries d ON d.id = s.dispensary_id
LEFT JOIN store_crawl_schedule scs ON scs.store_id = s.id
LEFT JOIN crawler_schedule cs_global ON cs_global.schedule_type = 'global_interval'
LEFT JOIN LATERAL (
SELECT * FROM crawl_jobs cj2
WHERE cj2.store_id = s.id
ORDER BY cj2.created_at DESC
LIMIT 1
) cj ON TRUE
WHERE s.active = TRUE;
-- Grant permissions
GRANT SELECT ON crawl_schedule_status TO dutchie;