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
- Queries
Product::whereIsPublic(false)->where('publication_reserve', '<', now()) - For each product: runs
PublishProductActioninside a DB transaction - Caution:
publication_reserveis overwritten withnow()— original scheduled date is lost after publish
SendLotteryWinnerEmailJob¶
Path: app/Modules/Lottery/Jobs/SendLotteryWinnerEmailJob.php
Status: Has uncommitted changes on staging branch.
- Queries
Lotteryrecords wherestatus = WON,is_sent = false,lottery_result_date < now() - Uses
GROUP_CONCAT(lotteries.id)— MySQL default truncation limit 1024 chars (risk with many winners) - Dispatches
LotteryWonevent per winner →LotteryWonListener→LotteryWonNotification(ShouldQueue) - Sets
is_sent = trueafterevent()dispatch but before queue worker delivers notification - No
withoutOverlapping()— overlapping runs possible if query + dispatch takes >1 min
SendToOricon¶
Path: app/Modules/Order/Jobs/SendToOricon.php
- Queries orders in shipping window
00:00–21:59JST - Exports TSV via
OriconTsvExport(Maatwebsite Excel) - Uploads to
oricon-sftpdisk (SFTP, SSH key-based) Throwableis caught and swallowed — SFTP failures are silent; no queue retry, no alert
SendToBillboard¶
Path: app/Modules/Order/Jobs/SendToBillboard.php
- Generates fixed-width ASCII text file (
\r\nline endings) - Hardcoded chain code
9650, store code557001 - Quantities >99,999 split across multiple rows
- FTP upload to
billboard-ftpdisk (plain FTP — no SFTP/TLS) - 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 onstaging). No overlap prevention + partial idempotency gap. Highest risk scheduled task.SendToOriconswallows allThrowable— failures are completely invisible.GROUP_CONCATMySQL 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 / SentrySendToOricon: failures silently caught — requires external monitoring (Oricon data freshness check)SendLotteryWinnerEmailJob: monitorlotteries.is_sentcolumn + queue depth for stuck winners