Skip to content

Scheduled Tasks

Flow Diagram

graph TB
    subgraph Scheduler["Scheduler (Asia/Tokyo)"]
        S1["Every minute"] --> J1["PublishJob"]
        S2["Every minute"] --> J2["SendLotteryWinnerEmailJob"]
        S3["Daily 22:00 JST"] --> J3["SendToOricon"]
        S4["Daily 22:00 JST"] --> J4["SendToBillboard"]
    end

    J1 --> A1["Product::whereIsPublic(false)\nwhere publication_reserve < now()\nPublishProductAction per product\nDB transaction"]

    J2 --> A2["Query WON lotteries\nis_sent=false\nlottery_result_date < now()\nGROUP_CONCAT ids"]
    A2 --> A3["LotteryWon event\nLotteryWonListener\nLotteryWonNotification (queued mail)"]
    A3 --> A4["is_sent = true"]

    J3 --> B1["OriconTsvExport\nSFTP to oricon-sftp disk\nshipping window 00:00-21:59"]
    B1 --> B2["Throwable caught\nsilent failure\nno retry"]

    J4 --> C1["Billboard fixed-width ASCII\nFTP to billboard-ftp disk\nchain 9650 / store 557001"]
    C1 --> C2["Exceptions propagate\njob fails + retries"]

Purpose

Four scheduled jobs handle time-sensitive automation: product publication, lottery winner notification, and music chart reporting to external agencies.

Schedule

All times JST (Asia/Tokyo). Kernel: app/Console/Kernel.php.

Job Class Schedule No Overlap Guard
Product publish Product\Jobs\PublishJob Every minute None
Lottery winner email Lottery\Jobs\SendLotteryWinnerEmailJob Every minute None
Oricon FTP export Order\Jobs\SendToOricon Daily 22:00 None
Billboard FTP export Order\Jobs\SendToBillboard Daily 22:00 None

Job Details

PublishJob

Path: app/Modules/Product/Jobs/PublishJob.php

  1. Queries Product::whereIsPublic(false)->where('publication_reserve', '<', now())
  2. For each product: runs PublishProductAction inside a DB transaction
  3. Caution: publication_reserve is overwritten with now() — original scheduled date is lost after publish

SendLotteryWinnerEmailJob

Path: app/Modules/Lottery/Jobs/SendLotteryWinnerEmailJob.php

Status: Has uncommitted changes on staging branch.

  1. Queries Lottery records where status = WON, is_sent = false, lottery_result_date < now()
  2. Uses GROUP_CONCAT(lotteries.id) — MySQL default truncation limit 1024 chars (risk with many winners)
  3. Dispatches LotteryWon event per winner → LotteryWonListenerLotteryWonNotification (ShouldQueue)
  4. Sets is_sent = true after event() dispatch but before queue worker delivers notification
  5. No withoutOverlapping() — overlapping runs possible if query + dispatch takes >1 min

SendToOricon

Path: app/Modules/Order/Jobs/SendToOricon.php

  1. Queries orders in shipping window 00:00–21:59 JST
  2. Exports TSV via OriconTsvExport (Maatwebsite Excel)
  3. Uploads to oricon-sftp disk (SFTP, SSH key-based)
  4. Throwable is caught and swallowed — SFTP failures are silent; no queue retry, no alert

SendToBillboard

Path: app/Modules/Order/Jobs/SendToBillboard.php

  1. Generates fixed-width ASCII text file (\r\n line endings)
  2. Hardcoded chain code 9650, store code 557001
  3. Quantities >99,999 split across multiple rows
  4. FTP upload to billboard-ftp disk (plain FTP — no SFTP/TLS)
  5. No try/catch — exceptions propagate, job fails and enters Laravel retry queue

Failure Paths

Job Failure Mode Impact
PublishJob DB transaction failure Products remain unpublished; retried next minute
SendLotteryWinnerEmailJob is_sent = true set before queue deliver If queue worker dies, winner is never notified — no re-send possible
SendLotteryWinnerEmailJob Duplicate run Duplicate emails possible without withoutOverlapping()
SendToOricon SFTP failure Silent — Oricon doesn't receive data, no alert triggered
SendToBillboard FTP failure Job fails, retries per queue config

Risk Flags

  • SendLotteryWinnerEmailJob: actively modified (uncommitted changes on staging). No overlap prevention + partial idempotency gap. Highest risk scheduled task.
  • SendToOricon swallows all Throwable — failures are completely invisible.
  • GROUP_CONCAT MySQL truncation at 1024 chars if many concurrent winners.
  • No withoutOverlapping() on any job — all four can run concurrently if previous run takes >1 min.

Observability

  • SendToBillboard: exceptions visible in Laravel logs / Sentry
  • SendToOricon: failures silently caught — requires external monitoring (Oricon data freshness check)
  • SendLotteryWinnerEmailJob: monitor lotteries.is_sent column + queue depth for stuck winners