Verified Commit f18d8f82 authored by Korko's avatar Korko
Browse files

Add button in org panel to DL final csv if available

parent 030ffb25
Pipeline #365306 passed with stages
in 4 minutes and 26 seconds
<?php
namespace App\Collections;
use App\Models\Participant;
use Illuminate\Database\Eloquent\Collection as BaseCollection;
class ParticipantsCollection extends BaseCollection
{
public function appendTargetToExclusions()
{
return (clone $this)->each(function (Participant $participant) {
$participant->exclusions->add($participant->target);
});
}
}
\ No newline at end of file
......@@ -40,6 +40,7 @@ class OrganizerController extends Controller
])
];
}),
'finalCsvAvailable' => $draw->next_solvable
]);
}
......@@ -81,7 +82,7 @@ class OrganizerController extends Controller
SendMail::dispatch($participant, new TargetDrawn($participant));
}
public function csv(Draw $draw)
public function csvInit(Draw $draw)
{
return response(
"\xEF\xBB\xBF".// UTF-8 BOM
......@@ -94,6 +95,23 @@ class OrganizerController extends Controller
);
}
public function csvFinal(Draw $draw)
{
abort_unless($draw->expired, 403, 'Cet évènement n\'est pas encore terminé');
abort_unless($draw->next_solvable, 404, 'Cet évènement ne permet pas cette génération');
return response(
"\xEF\xBB\xBF".// UTF-8 BOM
$draw->participants
->appendTargetToExclusions()
->toCsv(['name', 'email', 'exclusionsNames'])
->prepend([
['# Fichier généré le '.date('d-m-Y').' sur '.config('app.name').' ('.config('app.url').')'],
['# Ce fichier peut être utilisé pour préremplir les participants ainsi que les exclusions associées'],
])
);
}
public function delete(Request $request, Draw $draw)
{
$draw->delete();
......
......@@ -35,13 +35,6 @@ class Draw extends Model
'expires_at',
];
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = ['mail_title', 'mail_body', 'expires_at'];
public function save(array $options = [])
{
$this->expires_at = $this->expires_at ?: (new DateTime('now'))->add(new DateInterval('P7D'));
......@@ -83,7 +76,7 @@ class Draw extends Model
$this->resolveHash($value) :
parent::resolveRouteBinding($value, $field);
abort_if($draw === null || $draw->expired, 404);
abort_if($draw === null, 404);
return $draw;
}
......
......@@ -3,6 +3,7 @@
namespace App\Models;
use App\Casts\EncryptedString;
use App\Collections\ParticipantsCollection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
......@@ -98,4 +99,15 @@ class Participant extends Model
return $participant;
}
/**
* Create a new Eloquent Collection instance.
*
* @param array $models
* @return \Illuminate\Database\Eloquent\Collection
*/
public function newCollection(array $models = [])
{
return new ParticipantsCollection($models);
}
}
......@@ -43,6 +43,11 @@ class DrawHandler
$this->createParticipants($draw, $this->participants, $this->hat);
Metrics::increment('participants', count($this->participants));
if (! $this->isNextDrawSolvable($draw)) {
$draw->next_solvable = false;
$draw->save();
}
$this->sendOrganizerEmail($draw);
$this->sendDelayedOrganizerEmail($draw);
foreach ($draw->participants as $participant) {
......@@ -63,7 +68,6 @@ class DrawHandler
public function createParticipants(Draw $draw, array $participants, array $hat): void
{
$draw->participants = collect();
foreach ($participants as $idx => $santa) {
$mail = (new MailModel())->draw()->associate($draw);
$mail->save();
......@@ -76,7 +80,7 @@ class DrawHandler
$participant->save();
$participants[$idx]['model'] = $participant;
$draw->participants->add($participant);
$draw->participants()->save($participant);
}
foreach ($hat as $santaIdx => $targetIdx) {
......@@ -85,34 +89,38 @@ class DrawHandler
}
$draw->participants[$santaIdx]->target()->save($participants[$targetIdx]['model']);
$draw->participants[$santaIdx]->save();
}
}
public function sendOrganizerEmail(Draw $draw)
public function isNextDrawSolvable(Draw $draw): bool
{
SendMail::dispatch($draw->organizer, new OrganizerRecap($draw));
}
try {
$exclusions = [];
public function sendDelayedOrganizerEmail(Draw $draw)
{
$exclusions = [];
$draw->participants->each(function (&$participant) use (&$exclusions) {
// We don't attach so not in DB, just in the Collection
$participant->exclusions->push($participant->target);
$draw->participants->each(function (Participant $participant) use (&$exclusions) {
$exclusions[$participant->id] = array_merge(
$participant->exclusions->pluck('id')->all(),
[$participant->target->id]
);
});
$exclusions[$participant->id] = $participant->exclusions->pluck('id')->all();
});
Solver::one($draw->participants->pluck(null, 'id')->all(), $exclusions);
try {
// Check if there's a solution with the previous exclusions + the actual target
Solver::one($draw->participants->all(), $exclusions);
return true;
} catch (SolverException $exception) {
$exclusions = [];
return false;
}
}
public function sendOrganizerEmail(Draw $draw)
{
SendMail::dispatch($draw->organizer, new OrganizerRecap($draw));
}
public function sendDelayedOrganizerEmail(Draw $draw)
{
SendMail::dispatch($draw->organizer, new OrganizerFinalRecap($draw))
->delay($draw->expires_at->addDays(2));
->delay($draw->expires_at->addDay());
}
public function sendParticipantEmail(Participant $participant)
......
......@@ -23,7 +23,7 @@ class HatSolver implements SolverInterface
$hat = array_keys($participants);
shuffle($hat);
return $this->solve(0, [], $exclusions, $hat);
return $this->solve(array_keys($participants)[0], [], $exclusions, $hat);
}
private function solve(int $participantIdx, array $combination, array $allExclusions, array $currentHat) : Generator
......
......@@ -27,7 +27,8 @@ trait UpdatesMailDelivery
public function updateDelivery(MailModel $mail, $status, $version = null)
{
if (! isset($version) or $mail->version === (int)$version) {
// SQLite does not cast $mail->version as int and $version may be a string too so cast both
if (! isset($version) or (int)$mail->version === (int)$version) {
$mail->updateDeliveryStatus($status);
event(new MailStatusUpdated($mail));
......
......@@ -18,6 +18,7 @@ class CreateDrawsTable extends Migration
$table->longText('mail_title');
$table->longText('mail_body');
$table->date('expires_at');
$table->boolean('next_solvable')->default(true);
$table->timestamps();
});
}
......
......@@ -12082,7 +12082,7 @@ You also need to include this conditional style in the <head> of your HTML file
<!--[if !IE 7]>
<style type="text/css">
#wrap {display:table;height:100%}
#wrap {display: table;height: 100%}
</style>
<![endif]-->
......@@ -12544,18 +12544,22 @@ footer .row {
13. Tooltip
========================================================================== */
.tip {
.tip-wrapper {
display: inline-block;
position: relative;
border-bottom: 1px dotted #666;
text-align: left;
}
.tip h3 {
.tip-wrapper .tip-handler {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
.tip-content h3 {
margin: 12px 0;
}
.tip .tip-content {
.tip-content {
min-width: 300px;
max-width: 400px;
color: #EEEEEE;
......@@ -12573,47 +12577,47 @@ footer .row {
padding: 0;
}
.tip .right {
.tip-content.right {
top: 50%;
left: 100%;
margin-left: 20px;
transform: translate(0, -50%);
}
.tip .left {
.tip-content.left {
top: 50%;
right: 100%;
margin-right: 20px;
transform: translate(0, -50%);
}
.tip .top {
.tip-content.top {
top: -20px;
left: 50%;
transform: translate(-30%, -100%);
}
.tip .bottom {
.tip-content.bottom {
top: 40px;
left: 50%;
transform: translate(-50%, 0);
}
.tip:hover .tip-content {
.tip-wrapper:hover .tip-content {
visibility: visible;
opacity: 1;
}
.tip .tip-content img {
.tip-content img {
width: 400px;
border-radius: 8px 8px 0 0;
}
.tip .text-content {
.tip-content .text-content {
padding: 10px 20px;
}
.tip .right i {
.tip-content.right i {
position: absolute;
top: 50%;
right: 100%;
......@@ -12623,7 +12627,7 @@ footer .row {
overflow: hidden;
}
.tip .right i::after {
.tip-content.right i::after {
content: "";
position: absolute;
width: 12px;
......@@ -12635,7 +12639,7 @@ footer .row {
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.5);
}
.tip .left i {
.tip-content.left i {
position: absolute;
top: 50%;
left: 100%;
......@@ -12645,7 +12649,7 @@ footer .row {
overflow: hidden;
}
.tip .left i::after {
.tip-content.left i::after {
content: "";
position: absolute;
width: 12px;
......@@ -12657,7 +12661,7 @@ footer .row {
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.5);
}
.tip .top i {
.tip-content.top i {
position: absolute;
top: 100%;
left: 30%;
......@@ -12667,7 +12671,7 @@ footer .row {
overflow: hidden;
}
.tip .top i::after {
.tip-content.top i::after {
content: "";
position: absolute;
width: 15px;
......@@ -12678,7 +12682,7 @@ footer .row {
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.5);
}
.tip .bottom i {
.tip-content.bottom i {
position: absolute;
bottom: 100%;
left: 50%;
......@@ -12688,7 +12692,7 @@ footer .row {
overflow: hidden;
}
.tip .bottom i::after {
.tip-content.bottom i::after {
content: "";
position: absolute;
width: 12px;
......
......@@ -12080,7 +12080,7 @@ You also need to include this conditional style in the <head> of your HTML file
<!--[if !IE 7]>
<style type="text/css">
#wrap {display:table;height:100%}
#wrap {display: table;height: 100%}
</style>
<![endif]-->
......@@ -12542,18 +12542,22 @@ footer .row {
13. Tooltip
========================================================================== */
.tip {
.tip-wrapper {
display: inline-block;
position: relative;
border-bottom: 1px dotted #666;
text-align: left;
}
.tip h3 {
.tip-wrapper .tip-handler {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
.tip-content h3 {
margin: 12px 0;
}
.tip .tip-content {
.tip-content {
min-width: 300px;
max-width: 400px;
color: #EEEEEE;
......@@ -12571,47 +12575,47 @@ footer .row {
padding: 0;
}
.tip .right {
.tip-content.right {
top: 50%;
left: 100%;
margin-left: 20px;
transform: translate(0, -50%);
}
.tip .left {
.tip-content.left {
top: 50%;
right: 100%;
margin-right: 20px;
transform: translate(0, -50%);
}
.tip .top {
.tip-content.top {
top: -20px;
left: 50%;
transform: translate(-30%, -100%);
}
.tip .bottom {
.tip-content.bottom {
top: 40px;
left: 50%;
transform: translate(-50%, 0);
}
.tip:hover .tip-content {
.tip-wrapper:hover .tip-content {
visibility: visible;
opacity: 1;
}
.tip .tip-content img {
.tip-content img {
width: 400px;
border-radius: 8px 8px 0 0;
}
.tip .text-content {
.tip-content .text-content {
padding: 10px 20px;
}
.tip .right i {
.tip-content.right i {
position: absolute;
top: 50%;
right: 100%;
......@@ -12621,7 +12625,7 @@ footer .row {
overflow: hidden;
}
.tip .right i::after {
.tip-content.right i::after {
content: "";
position: absolute;
width: 12px;
......@@ -12633,7 +12637,7 @@ footer .row {
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.5);
}
.tip .left i {
.tip-content.left i {
position: absolute;
top: 50%;
left: 100%;
......@@ -12643,7 +12647,7 @@ footer .row {
overflow: hidden;
}
.tip .left i::after {
.tip-content.left i::after {
content: "";
position: absolute;
width: 12px;
......@@ -12655,7 +12659,7 @@ footer .row {
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.5);
}
.tip .top i {
.tip-content.top i {
position: absolute;
top: 100%;
left: 30%;
......@@ -12665,7 +12669,7 @@ footer .row {
overflow: hidden;
}
.tip .top i::after {
.tip-content.top i::after {
content: "";
position: absolute;
width: 15px;
......@@ -12676,7 +12680,7 @@ footer .row {
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.5);
}
.tip .bottom i {
.tip-content.bottom i {
position: absolute;
bottom: 100%;
left: 50%;
......@@ -12686,7 +12690,7 @@ footer .row {
overflow: hidden;
}
.tip .bottom i::after {
.tip-content.bottom i::after {
content: "";
position: absolute;
width: 12px;
......
......@@ -12080,7 +12080,7 @@ You also need to include this conditional style in the <head> of your HTML file
<!--[if !IE 7]>
<style type="text/css">
#wrap {display:table;height:100%}
#wrap {display: table;height: 100%}
</style>
<![endif]-->
......@@ -12542,18 +12542,22 @@ footer .row {
13. Tooltip
========================================================================== */
.tip {
.tip-wrapper {
display: inline-block;
position: relative;
border-bottom: 1px dotted #666;
text-align: left;
}
.tip h3 {
.tip-wrapper .tip-handler {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
.tip-content h3 {
margin: 12px 0;
}
.tip .tip-content {
.tip-content {
min-width: 300px;
max-width: 400px;
color: #EEEEEE;
......@@ -12571,47 +12575,47 @@ footer .row {
padding: 0;
}
.tip .right {
.tip-content.right {
top: 50%;
left: 100%;
margin-left: 20px;
transform: translate(0, -50%);
}
.tip .left {
.tip-content.left {
top: 50%;
right: 100%;
margin-right: 20px;
transform: translate(0, -50%);
}
.tip .top {
.tip-content.top {
top: -20px;
left: 50%;
transform: translate(-30%, -100%);
}
.tip .bottom {
.tip-content.bottom {
top: 40px;
left: 50%;
transform: translate(-50%, 0);
}
.tip:hover .tip-content {
.tip-wrapper:hover .tip-content {
visibility: visible;
opacity: 1;
}
.tip .tip-content img {
.tip-content img {
width: 400px;
border-radius: 8px 8px 0 0;
}
.tip .text-content {
.tip-content .text-content {
padding: 10px 20px;
}
.tip .right i {
.tip-content.right i {
position: absolute;
top: 50%;
right: 100%;
......@@ -12621,7 +12625,7 @@ footer .row {
overflow: hidden;
}
.tip .right i::after {
.tip-content.right i::after {