Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions Events.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,10 @@ public static function onCronBeforeAction($event): void
$competition->updateAttributes(['last_synced_at' => KickoffTime::dbAt($now)]);
$settings->set($stateKey, $now);

if ($report->isSuccess() && $report->updated > 0) {
if ($report->updated > 0) {
// Score regardless of partial errors: scoring is idempotent
// and a bad record (e.g. an undrawn knockout fixture) must
// not block scoring of the games that imported cleanly.
(new ScoringService($competition))->scoreAllFinishedGames();
(new MatchdayBonusService($competition))->awardForCompleteMatchdays();
}
Expand Down Expand Up @@ -221,7 +224,10 @@ private static function runSyncForActiveCompetitions($event, bool $syncFixtures)
$competition->updateAttributes(['last_synced_at' => KickoffTime::nowDb()]);
self::log($controller, "Kickoff results [{$competition->slug}]: " . $report->summary());

if ($report->isSuccess() && $report->updated > 0) {
if ($report->updated > 0) {
// Score regardless of partial errors: scoring is idempotent
// and a bad record (e.g. an undrawn knockout fixture) must
// not block scoring of the games that imported cleanly.
$scored = (new ScoringService($competition))->scoreAllFinishedGames();
self::log($controller, "Kickoff scoring [{$competition->slug}]: {$scored} tip(s) updated.");
}
Expand Down
6 changes: 6 additions & 0 deletions adapters/HumHubApiAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,12 @@ private function applyGame(
}
$homeExt = (string) ($matchData['home_external_id'] ?? '');
$awayExt = (string) ($matchData['away_external_id'] ?? '');
if ($homeExt === '' || $awayExt === '') {
// Knockout fixture whose teams aren't drawn yet — expected state,
// not an error. Skip so it doesn't poison the sync report.
$report->skipped++;
return;
}
$home = $teamsByExternalId[$homeExt] ?? null;
$away = $teamsByExternalId[$awayExt] ?? null;
if ($home === null || $away === null) {
Expand Down
18 changes: 16 additions & 2 deletions controllers/AdminController.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ public function actionSetupWm2026()
}

if (!$competition->save()) {
Yii::error('FWC 2026 setup: could not create competition: ' . implode(', ', $competition->getFirstErrors()), 'kickoff');
Yii::$app->session->setFlash('error', Yii::t(
'KickoffModule.base',
'Could not create competition: {errors}',
Expand All @@ -159,6 +160,13 @@ public function actionSetupWm2026()

$metadataReport = $adapter->applyMetadata($competition);

// Fixtures may already include finished games (e.g. set up mid-tournament),
// so score them now — syncFixtures alone doesn't, and waiting for cron
// would leave the competition showing finished games with no points.
(new ScoringService($competition))->scoreAllFinishedGames();
(new MatchdayBonusService($competition))->awardForCompleteMatchdays();
(new SpecialBetResolver())->autoResolveAll($competition);

$type = $fixturesReport->isSuccess() && $metadataReport->isSuccess() ? 'success' : 'warning';
$message = Yii::t(
'KickoffModule.base',
Expand Down Expand Up @@ -309,6 +317,7 @@ private function autoLoadInitialSchedule(Competition $competition): void
['summary' => $report->summary()],
));
} elseif (!$report->isSuccess()) {
Yii::error('Schedule auto-load reported: ' . $report->summary() . ' — ' . implode('; ', $report->errors), 'kickoff');
Yii::$app->session->setFlash('warning', Yii::t(
'KickoffModule.base',
'Schedule auto-load reported: {summary} — {errors}',
Expand Down Expand Up @@ -379,7 +388,10 @@ public function actionSyncResults($id)
$competition->updateAttributes(['last_synced_at' => KickoffTime::nowDb()]);
$this->flashReport($report, Yii::t('KickoffModule.base', 'Results sync'));

if ($report->isSuccess() && $report->updated > 0) {
if ($report->updated > 0) {
// Score regardless of partial errors: scoring is idempotent and a
// bad record (e.g. an undrawn knockout fixture) must not block
// scoring of the games that imported cleanly.
$tipCount = (new ScoringService($competition))->scoreAllFinishedGames();
$awarded = (new MatchdayBonusService($competition))->awardForCompleteMatchdays();
$msg = Yii::t('KickoffModule.base', '{n} tip(s) scored.', ['n' => $tipCount]);
Expand Down Expand Up @@ -832,7 +844,9 @@ private function flashReport(SyncReport $report, string $label): void
if ($report->isSuccess()) {
Yii::$app->session->setFlash('success', $message);
} else {
Yii::$app->session->setFlash('error', $message . ' — ' . implode('; ', $report->errors));
$message .= ' — ' . implode('; ', $report->errors);
Yii::$app->session->setFlash('error', $message);
Yii::error($message, 'kickoff');
}
}
}
2 changes: 2 additions & 0 deletions controllers/CompetitionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,7 @@ public function actionTips($slug)
));
}
if ($errors > 0) {
Yii::error("{$errors} tip(s) could not be saved.", 'kickoff');
Yii::$app->session->setFlash('error', Yii::t(
'KickoffModule.base',
'{n} tip(s) could not be saved.',
Expand Down Expand Up @@ -664,6 +665,7 @@ public function actionSpecialBetTips($slug)
));
}
if ($errors > 0) {
Yii::error("{$errors} special bet tip(s) could not be saved.", 'kickoff');
Yii::$app->session->setFlash('error', Yii::t(
'KickoffModule.base',
'{n} special bet tip(s) could not be saved.',
Expand Down
6 changes: 6 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Changelog
=========

1.0.8 (June 12, 2026)
---------------------
- Fix: Undrawn knockout fixtures no longer mark the sync as failed, which had blocked point calculation for the whole competition.
- Fix: Finished games are now scored after every sync and during FWC 2026 setup, not only on a fully error-free sync.
- Enh: Sync and tip-save failures are now written to the application log.

1.0.7 (June 9, 2026)
--------------------
- Enh: Banner added to info, rules and leaderboard pages; nav buttons reordered (Competition → Leaderboard → Rules → Info), admin moved to top-right; headings no longer repeat the competition name.
Expand Down
2 changes: 1 addition & 1 deletion module.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"world cup",
"leaderboard"
],
"version": "1.0.7",
"version": "1.0.8",
"humhub": {
"minVersion": "1.18"
},
Expand Down
Loading