SYNC 20 participate InstantWin Ok

This commit is contained in:
Jérémy Munsch 2023-12-12 12:25:45 +01:00
parent 9f73d89e5c
commit cf0b1a54f0
No known key found for this signature in database
GPG key ID: 18361AF3BFEC0FEC
51 changed files with 667 additions and 352 deletions

View file

@ -28,7 +28,11 @@ class CodeController extends Controller
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$nbCodeBase = Code::query()->count();
$nbCodeUsed = Code::query()->where('code_used', true)->count();
$nbCodeUsed = Code::query()->whereHas(
'participations',
operator: '>',
count: $settings->nb_unique_code_use
)->count();
$nbCodeExported = Code::query()->where('code_exported', true)->count();
$nbCodeRemainingToExport = Code::query()->where('code_exported', false)->count();
return view('back::pages.codes.index', compact(

View file

@ -40,7 +40,7 @@ class ProductController extends Controller
$search,
null,
'name',
'code_ean',
'ean_code',
);
}
$searchFields = \sprintf(

View file

@ -10,11 +10,13 @@ use App\Mail\NotifAdmin;
use App\Mail\NotifWinAdmin;
use App\Mail\PrizeWon;
use App\Mail\DiscountWonMail;
use App\Mail\Won\PrizeWonAdminMail;
use App\Models\Customer;
use App\Models\InstantWin;
use App\Models\Participation;
use App\Models\Discount;
use App\Models\Settings;
use App\Notifications\Customer\HasWonInstantWin;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
@ -40,6 +42,7 @@ class ParticipateController extends Controller
public function __construct()
{
$this->middleware(function (Request $request, callable $next) {
$this->authorize('fo.gameEnabled');
$this->assertUserHasEnoughInfoToUseMesBr();
return $next($request);
});
@ -71,8 +74,8 @@ class ParticipateController extends Controller
/** @var \App\Models\InstantWin|null */
$instantWin = InstantWin::query()->lockForUpdate()
->where('available_at', '<', now())
->where('won', false)
->get()->sortBy('available_at', 'ASC')->first();
->whereDoesntHave('participation')
->get()->sortBy('available_at')->first();
/** @var \App\Models\Settings */
$settings = app(Settings::class);
@ -81,9 +84,9 @@ class ParticipateController extends Controller
$customer = auth('frontend')->user();
/** @var \App\Models\Code */
$code = $request->post('code');
$code = $request->post('unique_code');
/** @var \App\Models\Product|null */
$product = $request->post('code');
$product = $request->post('product');
// * Make new participation
$participation = Participation::makeNew(
@ -94,6 +97,7 @@ class ParticipateController extends Controller
);
// * Si l'utilisateur actuel à déjà gagné un gros lot, pas d'instant gagnant.
// ? As-t-on un instant gagnant ?
if (Participation::customerAlreadyHasAnInstantWin($customer) or !$instantWin) {
// ? Les BR sont activés ?
if (!$settings->discounts_activated) {
@ -114,20 +118,28 @@ class ParticipateController extends Controller
}
// * Le joueur à remporté un instant gagnant
// Ici si fail car unique dupliqué catch.
try {
// Gain d'un gros lot.
$this->instantWin($request, $participation, $instantWin);
return view(
'theme::pages.participation.won_prize',
compact('settings')
)->with('prize', $instantWin->prize);
} catch (\ErrorException $e) {
// Unbind instant gagnant.
$participation->instantWin()->delete();
// phpcs:ignore
// TODO: si erreur il faut envoyer un mail à equipe.web@ldc.fr.
} //end try
$participation->instantWin()->save($instantWin);
$participation->save();
// * Notify customer for his win
$customer->notify(new HasWonInstantWin(
$instantWin->prize,
$customer->api_data->firstname
));
// * Dispatch notification to mail list
Mail::to($settings->notification_emails)->send(new PrizeWonAdminMail(
$settings->name,
$participation->created_at,
$instantWin->prize->label,
$customer->api_data,
$code->code_unique,
optional($product)->ean_code
));
return view('theme::pages.participation.won', [
'prizeModel' => $instantWin->prize
]);
});
}
@ -152,82 +164,6 @@ class ParticipateController extends Controller
// * --- PRIVATE METHODS ---
/**
* Handle instant wins
*
* @param string $serial
* @param \App\Models\Participation $participation
* @param \App\Models\InstantWin|null $instantWin
* @return void
*/
private function instantWin(
string $serial,
Participation $participation,
InstantWin|null $instantWin
): void {
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$instantWin->won = true;
// Should save now to prevent next player win on the same winning moment.
$instantWin->save();
$participation->instantWin()->save($instantWin);
// Envoi du courrier Arcadia.
if (
$instantWin->prize->arcadia_operation_code !== 0 &&
$instantWin->prize->arcadia_support_code !== 0
) {
$crmId = auth('backend')->user()->crm_id;
$fields = [
'root' => [
[
'fieldName' => 'order_date',
'fieldValue' => date('d/m/Y')
],
[
'fieldName' => 'operation',
'fieldValue' => $instantWin->prize->arcadia_operation_code
]
],
'orderLine' => [
[
'fieldName' => 'code_unique',
'fieldValue' => $request->serial
],
[
'fieldName' => 'support_code',
'fieldValue' => $instantWin->prize->arcadia_support_code
],
[
'fieldName' => 'label_type_support',
'fieldValue' => 'Gain du lot : ' . $instantWin->prize->label
]
]
];
if ($settings->use_ean_codes === 1) {
$codeEan = $request->code_ean;
$fields['orderLine'][] = [
'fieldName' => 'code_on_pack',
'fieldValue' => "$codeEan->code"
];
}
$arcadiaMail = $this->apiCrm->ArcadiaOutgoingMail($crmId, $fields, 'game');
if ($arcadiaMail->status === 'KO') {
Mail::to(config('mail.admin'))->send(new CourrierArcadiaError(auth('backend')->user()));
}
} //end if
Mail::to(auth('backend')->user()->email)->send(new PrizeWon(auth('backend')->user(), $instantWin->prize, $settings));
$mails = json_decode($settings->notification_emails);
foreach ($mails as $mail) {
Mail::to($mail->email)->send(new NotifWinAdmin($settings, $participation));
}
$participation->save();
}
/**
* Détermine si un BR doit être servi
* renvoi la vue de gain BR ou la vue "Perdu"
@ -319,11 +255,15 @@ class ParticipateController extends Controller
} //end try
// * Phase - 2 Mise à jour du coupon dans MesBr
/** @var \LDCWEB\MesBR */
$mesBR = app('mesBrAPI');
/** @var \LDC\MESBR\MesBr */
$mesBr = app('mesBrAPI');
$mesBr->updateCustomer($crmUser->hashcode, $crmUser->email, $crmUser->country);
$result = $mesBr->getMurCustomer($crmUser->user, $discount);
$mesBR->updatePanier($crmUser->user, $discount);
$mesBR->updateCustomer($crmUser->user);
$result = $mesBR->updatePanier($crmUser->user, $discount);
if ($result["status"] === "OK") {
$discount = new Discount();
$discount->link = $result["link"];
@ -353,7 +293,7 @@ class ParticipateController extends Controller
*/
private function assertUserHasEnoughInfoToUseMesBr()
{
// * Valider que le client dispose des donnés requises pour MesBR
// * Valider que le client dispose des donnés requises pour MesBr
try {
/** @var \App\Models\Customer */
$authUser = auth('frontend')->user();

View file

@ -41,10 +41,10 @@ class StoreProductRequest extends FormRequest
{
$rules = [
'name' => 'required|string|min:3|max:255',
'code_ean' => [
'ean_code' => [
'required',
'numeric',
'unique:products,code_ean',
'unique:products,ean_code',
'regex:/^[0-9]{7}$|^[0-9]{8}$|^[0-9]{13}$/',
],
'picture_alt' => 'required_with:picture|string|between:3,255',
@ -83,7 +83,7 @@ class StoreProductRequest extends FormRequest
'model' => trans('models.classes.product'),
]),
'picture' => trans('validation.attributes.image'),
'code_ean' => trans('validation.custom.ean_code'),
'ean_code' => trans('validation.custom.ean_code'),
'range_id' => trans('models.classes.range'),
];
}

View file

@ -3,6 +3,7 @@
namespace App\Http\Requests\Bo\Products;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule;
class UpdateProductRequest extends StoreProductRequest
{
@ -24,10 +25,10 @@ class UpdateProductRequest extends StoreProductRequest
public function rules()
{
return array_merge(parent::rules(), [
'code_ean' => [
'ean_code' => [
'required',
'numeric',
'unique:products,code_ean,' . $this->route('product')->id . ',id',
Rule::unique('products', 'ean_code')->ignore($this->route('product')->id),
'regex:/^[0-9]{7}$|^[0-9]{8}$|^[0-9]{13}$/',
],
]);

View file

@ -66,6 +66,7 @@ class UpdateSettingsRequest extends FormRequest
'use_ean_codes' => 'required|boolean',
'activated_teaser' => 'required|boolean',
'activated_close_page' => 'required|boolean',
// phpcs:ignore Generic.Files.LineLength.TooLong
'close_page_active_date' => 'nullable|required_if:activated_close_page,true,1|date|after_or_equal:end_date',
'redirect_close' => 'required|url:http,https',

View file

@ -7,8 +7,10 @@ use App\Models\Participation;
use App\Models\Product;
use App\Models\Settings;
use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use Kwaadpepper\Serial\Exceptions\SerialCasterException;
@ -19,8 +21,15 @@ class ParticipateRequest extends FormRequest
*
* @return boolean
*/
public function authorize()
public function authorize(): bool
{
/** @var \App\Models\Customer */
$customer = auth('frontend')->user();
// * Use rate limiter to prevent brute force sticker code
if (RateLimiter::tooManyAttempts("participate:{$customer->id}", $perMinute = 5)) {
return false;
}
RateLimiter::hit("participate:{$customer->id}");
return Gate::check('participate', Participation::class);
}
@ -29,25 +38,51 @@ class ParticipateRequest extends FormRequest
*
* @return void
*/
protected function prepareForValidation()
protected function prepareForValidation(): void
{
try {
$this->merge([
'code' => Code::query()->where('id', Code::serialToInt(
Str::upper(\strval($this->code))
'unique_code' => Code::query()->where('id', Code::serialToInt(
Str::upper(\strval($this->input('unique_code')))
))->first() ?? false,
]);
} catch (SerialCasterException) {
// * Provoquer l'affichage code incorrect.
$this->merge([
'code' => false,
'unique_code' => false,
]);
} finally {
$this->merge([
'code_ean' => (string)$this->string('code_ean')->padLeft(13, '0'),
'accept_rules' => $this->boolean('accept_rules')
'ean_code' => (string)$this->string('ean_code')->padLeft(13, '0'),
'game_rules' => $this->boolean('game_rules')
]);
}
/** @var \App\Models\Settings */
$settings = app(Settings::class);
// ? Does the game use EAN Codes ?
if ($settings->use_ean_codes) {
/**
* * Check si les codes ean sont utilisés, auquel cas
* * on test celui envoyé pour savoir s'il est correct.
* Modif demande vincent :
* Tester code ean existe avec 13, puis 8 puis 7 chars
* Si le code EAN est trouvé dans la liste, alors on prend l'objet correspondant,
* sinon on prend le premier objet qui contient les 7 1er chiffres (cas des EAN pds)
*
* Dans la base chaque code ean doit être rentré dans sa valeur absolue
* càd que les code ean 8 sont de longueur 8, 7 de longueur7 et 13 de longueur 13.
*/
$this->merge([
// * Union pour faire des recherches séparée en une seule requête.
// ! ean_code is already str_padded to 13
'product' => Product::query()->where('ean_code', $this->post('ean_code'))
->union(Product::query()->where('ean_code', Str::limit($this->post('ean_code'), 8, '')))
->union(Product::query()->where('ean_code', Str::limit($this->post('ean_code'), 7, '')))
->first()
]);
} //end if
}
/**
@ -55,41 +90,31 @@ class ParticipateRequest extends FormRequest
*
* @return array
*/
public function rules()
public function rules(): array
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$rules = [
'accept_rules' => 'required|boolean|accepted',
'serial' => [
'game_rules' => 'required|boolean|accepted',
'unique_code' => [
'required',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) use ($settings) {
function (string $attribute, $value, callable $fail) use ($settings) {
// ? Le Code unique est resolved ?
if ($value === false or !\is_object($value) or \get_class($value) !== Code::class) {
$fail(trans('Code unique incorrect, veuillez réessayer.'));
return;
}
/** @var \App\Models\Code $value */
// ? Code unique déjà trop utilisé ?
if ($value->code_used) {
$fail(trans('Ce code unique n\'est plus utilisable.'));
return;
}
/** Request réutilisable */
$participationQuery = Participation::query()
->whereHas('code', fn (Builder $q) => $q->where('id', $value->id));
/** @var \App\Models\Customer */
$customer = auth('frontend')->user();
// * Check si l'utilisateur n'a pas déjà utilisé ce code.
$query = (clone $participationQuery)->whereHas(
'customer',
fn (Builder $q) => $q->where('id', $customer->id)
);
$query = Participation::query()
->whereHas('code', fn (Builder $q) => $q->where('id', $value->id))
->whereHas('customer', fn (Builder $q) => $q->where('id', $customer->id));
// ! Une seule participation avec un code donné par joueur
if ($query->exists()) {
$fail(trans('Vous avez déjà utilisé ce code unique.'));
@ -98,40 +123,29 @@ class ParticipateRequest extends FormRequest
// * Gestion du nombre d'utilisation d'un code.
// ? Le code unique à été trop utilisé ?
if ((clone $participationQuery)->count() >= $settings->nb_unique_code_use) {
$value->code_used = true;
$value->saveOrFail();
$fail(trans('Ce code unique n\'est plus utilisable.'));
if ($value->has_been_used_too_many_times) {
// * Ne pas laisser d'indices, message générique
$fail(trans('Code unique incorrect, veuillez réessayer.'));
return;
}
}
]
];
/**
* * Check si les codes ean sont utilisés, auquel cas
* * on test celui envoyé pour savoir s'il est correct.
* Modif demande vincent :
* Tester code ean existe avec 13, puis 8 puis 7 chars
* Si le code EAN est trouvé dans la liste, alors on prend l'objet correspondant,
* sinon on prend le premier objet qui contient les 7 1er chiffres (cas des EAN pds)
*
* Dans la base chaque code ean doit être rentré dans sa valeur absolue
* càd que les code ean 8 sont de longueur 8, 7 de longueur7 et 13 de longueur 13.
*/
// ? Does the game use EAN Codes ?
if ($settings->use_ean_codes) {
$rules = \array_merge($rules, ['code_ean' => [
$rules = \array_merge($rules, ['ean_code' => [
'required',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) {
// * Union pour faire des recherches séparée en une seule requête.
// ! code_ean is already str_padded to 13
$productQuery = Product::query()->where('code_ean', $this->post('code_ean'))
->union(Product::where('code_ean', Str::limit($this->post('code_ean'), 8, '')))
->union(Product::where('code_ean', Str::limit($this->post('code_ean'), 7, '')));
if (!$productQuery->exists()) {
// phpcs:enable
/** @var \App\Models\Produt|mixed */
$product = $this->input('product');
if (
empty($product) or !is_object($product) or
!($product instanceof Model) or !$product->exists
) {
$fail(trans('Le code barre entré est incorrect, veuillez réessayer.'));
}
}
@ -145,12 +159,12 @@ class ParticipateRequest extends FormRequest
*
* @return array
*/
public function attributes()
public function attributes(): array
{
return [
'code_ean' => \trans('code barre'),
'serial' => \trans('code unique'),
'accept_rules' => \trans('règlements du jeu'),
'unique_code' => \trans('validation.custom.unique_code'),
'ean_code' => \trans('validation.custom.ean_code'),
'game_rules' => \trans('validation.custom.game_rules'),
];
}
}

View file

@ -63,7 +63,7 @@ class ProcessUniqueCode implements ShouldQueue, ShouldBeUnique
->getColumnListing((new Code())->getTable()))
->filter(function ($name) {
// * Ignorer les colonnes avec valeurs par défaut sur la table.
return !in_array($name, ['id', 'code_used', 'code_exported']);
return !in_array($name, ['id', 'code_exported']);
})->count() + 1;
/**

View file

@ -1,73 +0,0 @@
<?php
namespace LDCWEB;
class MesBR
{
private $endpoint;
private $refCamp;
public function __construct($endpoint, $refCamp)
{
$this->endpoint = $endpoint;
$this->refCamp = $refCamp;
}
public function createCustomer(string $hashcode, string $email, string $country)
{
return $this->callAPI("POST", '/customer', ['hashcode' => $hashcode, 'email' => $email, 'country' => $country]);
}
public function updateCustomer(string $hashcode, string $email, string $country)
{
return $this->callAPI("POST", '/customer', ['hashcode' => $hashcode, 'email' => $email, 'country' => $country]);
}
public function getMurCustomer(string $hashcode)
{
return $this->callAPI("GET", '/customer/mur', ['refcamp' => $this->refCamp, 'hashcode' => $hashcode]);
}
public function getMur()
{
return $this->callAPI("GET", '/mur', ['refcamp' => $this->refCamp]);
}
public function printCoupons(string $hashcode, array $coupons)
{
return $this->callAPI("GET", '/coupons/print', ['refcamp' => $this->refCamp, 'hashcode' => $hashcode, 'coupons' => implode(",", $coupons)]);
}
private function callAPI($method, $url, $data = [])
{
$url = $this->endpoint . '/api/v1' . $url;
$curl = curl_init();
switch ($method) {
case "POST":
curl_setopt($curl, CURLOPT_POST, 1);
if ($data) {
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
}
break;
case "PUT":
case "DELETE":
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
if ($data) {
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data));
}
break;
default:
if ($data) {
$url = sprintf("%s?%s", $url, http_build_query($data));
}
}
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_COOKIESESSION, true);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($curl);
if (!curl_errno($curl)) {
$http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if (!in_array($http_code, [200, 201])) {
return (object)['status' => 'KO', 'code' => 500, 'message' => 'Connection to API failed - Code HTTP : ' . $http_code];
}
} else {
return (object)['status' => 'KO', 'code' => 500, 'message' => 'Connection to API failed : ' . curl_error($curl)];
}
curl_close($curl);
return json_decode($result);
}
}

120
app/Lib/vendor/LDCWEB/MesBr.php vendored Normal file
View file

@ -0,0 +1,120 @@
<?php
namespace LDC\MESBR;
class MesBr
{
private $endpoint;
private $refCamp;
public function __construct($endpoint, $refCamp)
{
$this->endpoint = $endpoint;
$this->refCamp = $refCamp;
}
public function createCustomer(string $hashcode, string $email, string $country)
{
return $this->callApi("POST", '/customer', ['hashcode' => $hashcode, 'email' => $email, 'country' => $country]);
}
public function updateCustomer(string $hashcode, string $email, string $country)
{
return $this->callApi("POST", '/customer', ['hashcode' => $hashcode, 'email' => $email, 'country' => $country]);
}
public function getMurCustomer(string $hashcode)
{
return $this->callApi("GET", '/customer/mur', ['refcamp' => $this->refCamp, 'hashcode' => $hashcode]);
}
public function getMur()
{
return $this->callApi("GET", '/mur', ['refcamp' => $this->refCamp]);
}
public function printCoupons(string $hashcode, array $coupons)
{
return $this->callApi("GET", '/coupons/print', [
'refcamp' => $this->refCamp, 'hashcode' => $hashcode, 'coupons' => implode(",", $coupons)
]);
}
private function callApi($method, $url, $data = [])
{
$url = $this->endpoint . '/api/v1' . $url;
$curl = curl_init();
switch ($method) {
case "POST":
curl_setopt($curl, CURLOPT_POST, 1);
if ($data) {
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
}
break;
case "PUT":
case "DELETE":
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
if ($data) {
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data));
}
break;
default:
if ($data) {
$url = sprintf("%s?%s", $url, http_build_query($data));
}
}
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_COOKIESESSION, true);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($curl);
if (!curl_errno($curl)) {
$http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if (!in_array($http_code, [200, 201])) {
return (object)[
'status' => 'KO', 'code' => 500, 'message' => 'Connection to API failed - Code HTTP : ' . $http_code
];
}
} else {
return (object)[
'status' => 'KO', 'code' => 500, 'message' => 'Connection to API failed : ' . curl_error($curl)
];
}
curl_close($curl);
return json_decode($result);
}
// * Added METHODS
/** Update panier *
* @param $oCrmUser
* @param $couponId
* @param int $quantity
* @return array
*/
public function updatePanier($oCrmUser, $couponId, $quantity = 1)
{
$datas = [
'refsite' => $this->refSite,
'refcamp' => $this->refCamp,
'refadherent' => $oCrmUser->crm_id,
'nom' => $oCrmUser->lastname,
'prenom' => $oCrmUser->firstname,
'email' => $oCrmUser->email,
'code_postal' => $oCrmUser->postcode,
'commune' => $oCrmUser->city,
'pays' => $this->getCountry($oCrmUser->country),
'panier' => "($quantity)$couponId"
];
$res = $this->callAPI("GET", "/updatebasket", $datas);
if (!$res) {
error_log("MESBR - CODE : 999 - MESSAGE : Cannot read API Response (non json)");
return ['status' => 'KO', 'code' => 999, 'message' => 'Cannot read API Response (non json)'];
}
if ($res->Statut === 'KO') {
error_log("MESBR - CODE : $res->Code - MESSAGE : $res->Message");
return ['status' => 'KO', 'code' => $res->Code, 'message' => $res->Message];
}
$panierVerification = $this->endpoint . '/showbasket?basketid=' . $res->Panier->Panier;
$panier_link = $this->endpoint . '/?basketid=' . $res->Panier->Panier;
return ['status' => 'OK', 'link' => $panier_link, 'verif' => $panierVerification, 'basketId' => $res->Panier->Panier];
}
}

View file

@ -1,41 +0,0 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class NotifAdmin extends Mailable implements ShouldQueue
{
use Queueable;
use SerializesModels;
/** @var string */
private $msg;
/**
* Create a new message instance.
*
* @param string $msg
* @return void
*/
public function __construct(string $msg)
{
$this->msg = $msg;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$msg = $this->msg;
return $this->subject("{$settings->name} : Erreur")->view('front.basic.notif-admin', compact('msg'));
}
}

View file

@ -10,6 +10,7 @@ use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\View;
class DiscountWonMail extends Mailable implements ShouldQueue
{
@ -56,6 +57,7 @@ class DiscountWonMail extends Mailable implements ShouldQueue
*/
public function content(): \Illuminate\Mail\Mailables\Content
{
View::addNamespace('theme', resource_path('theme/mounted/views'));
return new Content(
markdown: 'theme::mails.won-discount',
with: [

View file

@ -0,0 +1,90 @@
<?php
namespace App\Mail\Won;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Queue\SerializesModels;
use LDC\LDCCRM\Models\Customer;
class PrizeWonAdminMail extends Mailable implements ShouldQueue
{
use Queueable;
use SerializesModels;
/**
* Create a new message instance.
*
* @param string $game_name The game name.
* @param \Carbon\Carbon $participation_date The customer participation date.
* @param string $prize_label The won prize label.
* @param \LDC\LDCCRM\Models\Customer $customer The customer who won.
* @param string $unique_code The unique code used to participate.
* @param string|null $ean_code The product code used to participate if any.
*
* @return void
*/
public function __construct(
protected string $game_name,
protected Carbon $participation_date,
protected string $prize_label,
protected Customer $customer,
protected string $unique_code,
protected string $ean_code
) {
}
/**
* Get the message envelope.
*
* @return \Illuminate\Mail\Mailables\Envelope
*/
public function envelope(): \Illuminate\Mail\Mailables\Envelope
{
return new Envelope(
subject: trans('Votre gain du jeu :game_name', [
'game_name' => $this->game_name
]),
);
}
/**
* Get the message content definition.
*
* @return \Illuminate\Mail\Mailables\Content
*/
public function content(): \Illuminate\Mail\Mailables\Content
{
$participation = [
trans('Participation :'),
"Code Unique : {$this->unique_code}",
];
if ($this->ean_code) {
$participation[] = "Code EAN : {$this->ean_code}";
}
return new Content(
htmlString: (new MailMessage())
->greeting(trans('Bonjour,'))
->line(trans(
'Le lot suivant a été gagné :prize_label le :date',
[
'prize_label' => $this->prize_label,
'date' => $this->participation_date->isoFormat('LLLL')
]
))->lines([
trans('Gagnant :'),
"{$this->customer->lastname} {$this->customer->firstname}",
"{$this->customer->address}",
"{$this->customer->address2}",
"{$this->customer->postcode} {$this->customer->city} - {$this->customer->country->label}",
"{$this->customer->email}",
"{$this->customer->phone}",
])->lines($participation)->render()
);
}
}

View file

@ -10,6 +10,7 @@ use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\View;
class PrizeWonMail extends Mailable implements ShouldQueue
{
@ -56,6 +57,8 @@ class PrizeWonMail extends Mailable implements ShouldQueue
*/
public function content(): \Illuminate\Mail\Mailables\Content
{
// * Fix for async usage
View::addNamespace('theme', resource_path('theme/mounted/views'));
return new Content(
markdown: 'theme::mails.won-prize',
with: [

View file

@ -2,18 +2,20 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
use Kwaadpepper\Serial\SerialCaster;
/**
* Code.
*
* @property string $code_unique The unique code matching this code id.
* @property boolean $code_used Is the code used too much ?
* @property boolean $code_exported Is the code exported ?
* @property \Illuminate\Support\Carbon $date_creation Code creation date.
*
* @property-read string|null $code_unique The unique code
* @property-read string|null $code_unique The unique code
* @property-read boolean $has_been_used_too_many_times Has this code been used too many times ?
*
* @method public static function intToSerial($int) Generate a serial from an int.
* @method public static function serialToInt($serial) Get an int from a serial.
@ -36,7 +38,6 @@ class Code extends Model
* @var array
*/
protected $fillable = [
'code_used',
'code_exported',
'date_creation'
];
@ -44,10 +45,9 @@ class Code extends Model
/**
* The attributes that should be cast.
*
* @var array<string, string>
* @var array<string,string>
*/
protected $casts = [
'code_used' => 'bool',
'code_exported' => 'bool',
'date_creation' => 'date',
];
@ -59,6 +59,20 @@ class Code extends Model
*/
public $timestamps = false;
// * EVENTS
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
static::created(fn () => Cache::forget('codes.count'));
static::updated(fn () => Cache::forget('codes.count'));
static::deleted(fn () => Cache::forget('codes.count'));
}
// * RELATIONS
/**
@ -71,18 +85,23 @@ class Code extends Model
return $this->hasMany(Participation::class, 'code_unique_id');
}
// * FUNCTIONS
// * METHODS
/**
* Generate code_unique attribute
*
* @return string|null
*/
public function getCodeUniqueAttribute()
public function codeUnique(): Attribute
{
if (!$this->id or !\is_integer($this->id) or $this->id < 1) {
return null;
}
return self::intToSerial($this->id);
return Attribute::make(
get: function (): ?string {
if (!$this->id or !\is_integer($this->id) or $this->id < 1) {
return null;
}
return self::intToSerial($this->id);
}
);
}
/**
@ -127,4 +146,26 @@ class Code extends Model
config('codes.codeCharList')
);
}
// * GETTERS
/**
* Get has_been_used_too_namy_times attribute
*
* @return \Illuminate\Database\Eloquent\Casts\Attribute
*/
public function hasBeenUsedTooManyTimes(): Attribute
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
return Attribute::make(
get: fn (): bool => self::query()
->where('id', $this->id)
->whereHas(
'participations',
operator: '>',
count: $settings->nb_unique_code_use
)->exists()
);
}
}

View file

@ -8,6 +8,7 @@ use App\Notifications\Customer\ResetPassword;
use App\Notifications\Customer\VerifyEmail;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Contracts\Database\Query\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Prunable;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
@ -56,6 +57,8 @@ class Customer extends Authenticatable implements MustVerifyEmail
'email_verified_at' => 'datetime',
];
// * EVENTS
/**
* Perform any actions required after the model boots.
*
@ -71,7 +74,7 @@ class Customer extends Authenticatable implements MustVerifyEmail
static::assertEmailIsUnique($model->id, $model->email);
});
static::deleting(function () {
throw new \RuntimeException('This cannot be done à cause de MesBR');
throw new \RuntimeException('This cannot be done à cause de MesBr');
});
static::deleted(function (self $model) {
if ($model->forceDeleting) {
@ -241,22 +244,23 @@ class Customer extends Authenticatable implements MustVerifyEmail
// * --- END AUTH ---
/**
* Get the crm Data of a customer
* Get api_data attribute, the crm Data of a customer
*
* @return \LDC\LDCCRM\Models\Customer
* @throws \LDC\LDCCRM\Exceptions\CrmException If cannot fetch data from crm.
* @return \Illuminate\Database\Eloquent\Casts\Attribute
*/
public function getApiDataAttribute(): CrmCustomer
public function apiData(): Attribute
{
return self::cacheCrm($this->hashcode, "apiData", function () {
/** @var \LDC\LDCCRM\CrmApiV2 */
$crm = app('crmAPI');
$crmCustomer = $crm->getCustomer($this->hashcode);
if (!$crmCustomer) {
throw new CrmException('CRM get Customer returned null');
}
return $crmCustomer;
});
return Attribute::make(
get: fn (): CrmCustomer => self::cacheCrm($this->hashcode, "apiData", function () {
/** @var \LDC\LDCCRM\CrmApiV2 */
$crm = app('crmAPI');
$crmCustomer = $crm->getCustomer($this->hashcode);
if (!$crmCustomer) {
throw new CrmException('CRM get Customer returned null');
}
return $crmCustomer;
})
);
}
/**

View file

@ -39,6 +39,8 @@ class FaqQuestion extends Model
*/
protected $with = ['theme'];
// * EVENTS
/**
* Perform any actions required after the model boots.
*

View file

@ -30,6 +30,8 @@ class FaqTheme extends Model
*/
protected $fillable = ['name'];
// * EVENTS
/**
* Perform any actions required after the model boots.
*

View file

@ -2,19 +2,21 @@
namespace App\Models;
use App\Traits\Models\IsCachedAsCollection;
use Illuminate\Database\Eloquent\Model;
/**
* InstantWin.
*
* @property \Illuminate\Support\Carbon $available_at When the instant win will be available.
* @property boolean $won Is the prize won ?
*
* @property-read \App\Models\Prize $prize Associated prize.
* @property-read \App\Models\Participation $participation Associated participation.
*/
class InstantWin extends Model
{
use IsCachedAsCollection;
/**
* Indicates if the model should be timestamped.
*
@ -29,14 +31,19 @@ class InstantWin extends Model
*/
protected $with = ['prize'];
// * EVENTS
/**
* The attributes that should be cast to native types.
* Perform any actions required after the model boots.
*
* @var array
* @return void
*/
protected $casts = [
'won' => 'bool'
];
protected static function booted()
{
static::creating(fn () => self::removeCached());
static::updating(fn () => self::removeCached());
static::deleting(fn () => self::removeCached());
}
// * RELATIONSHIPS

View file

@ -17,7 +17,7 @@ use IPLib\Address\AddressInterface;
* @property-read \Illuminate\Support\Carbon $created_at Creation Date.
* @property-read \Illuminate\Support\Carbon $updated_at Last update Date.
*
* @property-read string|null $code_ean Accessor for code_ean, from product table.
* @property-read string|null $ean_code Accessor for ean_code, from product table.
*
* @property-read \App\Models\Customer $customer Associated customer.
* @property-read \App\Models\Code $code Associated code.
@ -82,14 +82,14 @@ class Participation extends Model
// * GETTERS
/**
* Accessor for code_ean, from product table.
* Accessor for ean_code, from product table.
*
* @return \Illuminate\Database\Eloquent\Casts\Attribute;
*/
public function codeEan(): Attribute
public function eanCode(): Attribute
{
return Attribute::make(
get: fn () => optional($this->product)->code_ean,
get: fn () => optional($this->product)->ean_code,
);
}
@ -129,7 +129,7 @@ class Participation extends Model
public static function customerAlreadyHasAnInstantWin(Customer $customer): bool
{
return self::query()->whereHas('instantWin')
->whereHas('user', fn (Builder $q) => $q->where('id', $customer->id))
->whereHas('customer', fn (Builder $q) => $q->where('id', $customer->id))
->exists();
}
}

View file

@ -48,6 +48,8 @@ class Prize extends Model
'pdf_winning_proof'
];
// * EVENTS
/**
* Perform any actions required after the model boots.
*

View file

@ -14,7 +14,7 @@ use Illuminate\Database\Eloquent\Model;
* @property string $picture Path of the picture
* @property string $picture_alt Alt text for the picture
* @property string $picture_title Title text for the picture
* @property integer $code_ean Ean code
* @property integer $ean_code Ean code
* @property-read \Illuminate\Support\Carbon $created_at Creation Date.
* @property-read \Illuminate\Support\Carbon $updated_at Last update Date.
*
@ -37,7 +37,7 @@ class Product extends Model
'picture',
'picture_alt',
'picture_title',
'code_ean',
'ean_code',
'range_id',
];

View file

@ -6,7 +6,10 @@ use App\Casts\AsArrayOfDiscount;
use App\Lib\Discount;
use App\Lib\Helpers\FileStorageHelper;
use App\Traits\Models\IsCachedAsCollection;
use Carbon\CarbonInterval;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
/**
* Settings.
@ -87,6 +90,8 @@ class Settings extends Model
'notification_emails' => 'array',
];
// * EVENTS
/**
* Perform any actions required after the model boots.
*
@ -127,6 +132,46 @@ class Settings extends Model
return FileStorageHelper::onlyUploadedFilesWithFileStorageHelper(collect($files)->filter()->all());
}
/**
* Check if the game can be play and report if it cannot.
*
* Demande Vincent: Methods to check via policy if game can be played
*
* Besoin:
* - Min 1 code unique
* - Min 1 Instant gagnant
* - Min 1 Product si Settings use_ean_codes
* - Min 1 WebCoupon si Settings discounts_activated
*
* @return boolean
*/
public function checkGameCanBePlayed(): bool
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
if (!(bool)Prize::getCached()->count()) {
report(new \RuntimeException('Need prizes to enable the game'));
return false;
}
if (!(bool)Cache::remember('codes.count', CarbonInterval::day(), fn () => Code::query()->count())) {
report(new \RuntimeException('Need codes to enable the game'));
return false;
}
if (!(bool)InstantWin::getCached()->count()) {
report(new \RuntimeException('Need instant win to enable the game'));
return false;
}
if ($settings->use_ean_codes and !(bool)Product::getCached()->count()) {
report(new \RuntimeException('Need products to enable the game using ean codes'));
return false;
}
if ($settings->discounts_activated and !(bool)collect($this->web_coupons)->count()) {
report(new \RuntimeException('Need MesBr coupons to enable the game using discounts'));
return false;
}
return true;
}
// * GETTERS
/**
@ -134,11 +179,13 @@ class Settings extends Model
*
* The game has started ?
*
* @return boolean
* @return \Illuminate\Database\Eloquent\Casts\Attribute
*/
public function getGameHasBegunAttribute(): bool
public function gameHasBegun(): Attribute
{
return $this->start_date->lte(now());
return Attribute::make(
get: fn (): bool => $this->start_date->lte(now())
);
}
/**
@ -146,11 +193,13 @@ class Settings extends Model
*
* The game has ended ?
*
* @return boolean
* @return \Illuminate\Database\Eloquent\Casts\Attribute
*/
public function getGameHasEndedAttribute(): bool
public function gameHasEnded(): Attribute
{
return $this->game_has_begun and $this->end_date->lte(now());
return Attribute::make(
get: fn (): bool => $this->game_has_begun and $this->end_date->lte(now())
);
}
/**
@ -158,11 +207,13 @@ class Settings extends Model
*
* The game is oppen ?
*
* @return boolean
* @return \Illuminate\Database\Eloquent\Casts\Attribute
*/
public function getGameIsOpenAttribute(): bool
public function gameIsOpen(): Attribute
{
return $this->game_has_begun and !$this->game_has_ended;
return Attribute::make(
get: fn (): bool => $this->game_has_begun and !$this->game_has_ended
);
}
/**
@ -170,13 +221,15 @@ class Settings extends Model
*
* The teaser page has to be displayed ?
*
* @return boolean
* @return \Illuminate\Database\Eloquent\Casts\Attribute
*/
public function getShouldDisplayTeaserPageAttribute(): bool
public function shouldDisplayTeaserPage(): Attribute
{
return !$this->game_has_begun and
!$this->game_has_ended and
$this->activated_teaser;
return Attribute::make(
get: fn (): bool => !$this->game_has_begun and
!$this->game_has_ended and
$this->activated_teaser
);
}
/**
@ -184,13 +237,15 @@ class Settings extends Model
*
* The end page has to be displayed ?
*
* @return boolean
* @return \Illuminate\Database\Eloquent\Casts\Attribute
*/
public function getShouldDisplayEndPageAttribute(): bool
public function shouldDisplayEndPage(): Attribute
{
return $this->game_has_begun and
$this->game_has_ended and
$this->activated_close_page and
$this->close_page_active_date->gte(now());
return Attribute::make(
get: fn (): bool => $this->game_has_begun and
$this->game_has_ended and
$this->activated_close_page and
$this->close_page_active_date->gte(now())
);
}
}

View file

@ -45,6 +45,8 @@ class StaticPage extends Model
'type' => StaticPageType::class,
];
// * EVENTS
/**
* Perform any actions required after the model boots.
*

View file

@ -4,6 +4,7 @@ namespace App\Models;
use App\Enums\Users\RoleEnum;
use App\Traits\Models\ActivityLog;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
@ -64,6 +65,8 @@ class User extends Authenticatable
'published' => 'boolean',
];
// * EVENTS
/**
* Perform any actions required after the model boots.
*

View file

@ -2,6 +2,7 @@
namespace App\Notifications\Customer;
use App\Models\Settings;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

View file

@ -2,6 +2,7 @@
namespace App\Notifications\Customer;
use App\Models\Settings;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;

View file

@ -0,0 +1,63 @@
<?php
namespace App\Notifications\Customer;
use App\Mail\Won\PrizeWonMail;
use App\Models\Customer;
use App\Models\Prize;
use App\Models\Settings;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Mail\Mailable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailables\Address;
use Illuminate\Notifications\Notification;
class HasWonInstantWin extends Notification implements ShouldQueue
{
use Queueable;
/**
* Create a new notification instance.
*
* @param \App\Models\Prize $prize The prize that has been won.
* @param string $first_name The client first name.
* @return void
*/
public function __construct(protected Prize $prize, protected string $first_name)
{
}
/**
* Get the notification's delivery channels.
*
* @param object $notifiable
* @return array<int, string>
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function via(object $notifiable): array
{
// phpcs:enable
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param \App\Models\Customer $notifiable
* @return \Illuminate\Contracts\Mail\Mailable
*/
public function toMail(Customer $notifiable): Mailable
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
return (new PrizeWonMail(
new Address(
$notifiable->email,
$this->first_name
),
$settings->name,
$this->prize,
config('app.url')
));
}
}

View file

@ -2,6 +2,7 @@
namespace App\Notifications\Customer;
use App\Models\Settings;
use Illuminate\Auth\Notifications\ResetPassword as NotificationsResetPassword;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Facades\URL;

View file

@ -2,6 +2,7 @@
namespace App\Notifications\Customer;
use App\Models\Settings;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;

View file

@ -2,6 +2,7 @@
namespace App\Notifications\Customer;
use App\Models\Settings;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;

View file

@ -2,6 +2,7 @@
namespace App\Notifications\Customer;
use App\Models\Settings;
use Illuminate\Auth\Notifications\VerifyEmail as NotificationsVerifyEmail;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Carbon;

View file

@ -0,0 +1,54 @@
<?php
namespace App\Notifications\InstantWin;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class HasBennWon extends Notification
{
use Queueable;
/**
* Create a new notification instance.
*/
public function __construct()
{
//
}
/**
* Get the notification's delivery channels.
*
* @return array<int, string>
*/
public function via(object $notifiable): array
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*/
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->line('The introduction to the notification.')
->action('Notification Action', url('/'))
->line('Thank you for using our application!');
}
/**
* Get the array representation of the notification.
*
* @return array<string, mixed>
*/
public function toArray(object $notifiable): array
{
return [
//
];
}
}

View file

@ -7,6 +7,7 @@ use App\Enums\Users\RoleEnum;
use App\Models\Customer;
use App\Models\FaqQuestion;
use App\Models\Product;
use App\Models\Settings;
use App\Models\User;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
@ -53,6 +54,16 @@ class AuthServiceProvider extends ServiceProvider
(bool)FaqQuestion::getCached()->count()
);
Gate::define(
'fo.gameEnabled',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
function (User|Customer|null $user) {
/** @var \App\Models\Settings */
$settings = app(Settings::class);
return $settings->checkGameCanBePlayed();
}
);
$this->app->auth->provider('crm', function ($app, array $config) {
return new CrmUserProvider($app['hash'], $config['model']);
});

View file

@ -5,7 +5,7 @@ namespace App\Providers;
use App\Models\Settings;
use Illuminate\Support\Facades\App;
use Illuminate\Support\ServiceProvider;
use LDCWEB\MesBR;
use LDC\MESBR\MesBr;
class MesBrApiServiceProvider extends ServiceProvider
{
@ -20,7 +20,7 @@ class MesBrApiServiceProvider extends ServiceProvider
/** @var \App\Models\Settings|null */
$settings = app(Settings::class);
return new MesBR(
return new MesBr(
config('mesbr.endpoint'),
$settings->discounts_site,
$settings->discounts_campaign

View file

@ -79,9 +79,9 @@ class RouteServiceProvider extends ServiceProvider
}
// * Flatten if needed
$cspReport = collect(request()->json('csp-report'))
->flatMap(function ($v, $k) {
return [$k => is_array($v) ? collect($v)->flatten()->implode(', ') : $v];
})->all();
->flatMap(function ($v, $k) {
return [$k => is_array($v) ? collect($v)->flatten()->implode(', ') : $v];
})->all();
// * Filter array keys
$cspReport = collect(array_intersect_key($cspReport, [
'blocked-uri' => '', 'document-uri' => '', 'original-policy' => '',
@ -93,7 +93,7 @@ class RouteServiceProvider extends ServiceProvider
$cspReport->implode("\n")
));
})->middleware('throttle:5,2')
->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class)
->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class)
->name('cspReportUri');
}
}

View file

@ -65,7 +65,7 @@
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/",
"LDCWEB\\": "app/Lib/vendor/LDCWEB"
"LDC\\MESBR\\": "app/Lib/vendor/LDCWEB"
}
},
"scripts": {

View file

@ -19,7 +19,7 @@ class ProductFactory extends Factory
return [
'name' => 'product ' . \fake()->jobTitle(),
// * EAN 13 generate
'code_ean' => fake()->randomNumber(8, true) . fake()->randomNumber(5, true),
'ean_code' => fake()->randomNumber(8, true) . fake()->randomNumber(5, true),
'picture_alt' => fake()->sentence(),
'picture_title' => fake()->sentence(),
];

View file

@ -15,7 +15,6 @@ return new class extends Migration
{
Schema::create('codes', function (Blueprint $table) {
$table->unsignedBigInteger('id')->primary();
$table->boolean('code_used')->default(0)->comment('Is the code used too much ?');
$table->boolean('code_exported')->default(0)->comment('Has the code been exported ?');
$table->date('date_creation')->comment('Code creation date');
});

View file

@ -20,7 +20,7 @@ return new class extends Migration
$table->string('picture_alt')->nullable()->comment('Attribut alt de l\'image du produit');
$table->string('picture_title')->nullable()->comment('Attribut title de l\'image du produit');
$table->unsignedBigInteger('code_ean')->comment('Code EAN du produit pour la participation au jeu');
$table->unsignedBigInteger('ean_code')->comment('Code EAN du produit pour la participation au jeu');
$table->foreignId('range_id')->before('created_at')->references('id')
->on('ranges')->onDelete('cascade');

View file

@ -17,7 +17,6 @@ return new class extends Migration
$table->id();
$table->timestamp('available_at')
->comment('La date-heure à laquelle l\'instant gagnant pourras être gagné');
$table->boolean('won')->default(0)->comment('L\'instant à été gagné ?');
$table->foreignId('prize_id')->comment('Le lot qui à été gagné')->constrained('prizes');
$table->foreignId('participation_id')

View file

@ -4,6 +4,7 @@ namespace Database\Seeders\Development;
use App\Lib\Helpers\FileStorageHelper;
use App\Models\Prize;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
@ -11,6 +12,8 @@ use Illuminate\Support\Facades\Schema;
class PrizeSeeder extends Seeder
{
use WithoutModelEvents;
/**
* Run the database seeds.
*

View file

@ -238,6 +238,7 @@ return [
'ip' => 'IP',
'code' => 'code',
'unique_code' => 'code unique',
'product' => 'produit',
'value' => 'valeur',
'question' => 'question',
@ -268,5 +269,6 @@ return [
'discounts_site' => 'référence du site',
'discounts_campaign' => 'référence de la campagne',
'analytics' => 'analytics',
'game_rules' => 'règlement du jeu',
],
];

View file

@ -1,6 +1,5 @@
{{-- Meta --}}
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
{{-- blade-formatter-disable --}}
@hasSection('metaIndex')
<meta name="robots" content="@yield('metaIndex')">

View file

@ -18,9 +18,9 @@
<dt><span class="h4">{{ __('Voici l\'état du code trouvé') }}</span></dt>
<dd>{{ __('Code') }}&nbsp;: <strong>{{ $code->code_unique }}</strong></dd>
<dd>{{ __('ID') }}&nbsp;: <strong>{{ $code->id }}</strong></dd>
<dd>{{ __('Utilisé ?') }}&nbsp;:
<dd>{{ __('À atteint son nombre maximal d\'utilisation ?') }}&nbsp;:
<strong>
@if ($code->code_used)
@if ($code->has_been_used_too_many_times)
{{ Str::ucfirst(__('oui')) }}
@else
{{ Str::ucfirst(__('non')) }}

View file

@ -17,7 +17,7 @@
{{ __('Top 3 des codes EAN les plus utilisés') }} :
<ul class="list-group list-group-horizontal">
@foreach ($codesEan as $codeEan)
<li class="list-group-item">{{ $codeEan->code_ean }} : <b>{{ $codeEan->nb_usage }}</b></li>
<li class="list-group-item">{{ $codeEan->ean_code }} : <b>{{ $codeEan->nb_usage }}</b></li>
@endforeach
</ul>
@endif

View file

@ -104,7 +104,7 @@
</div>
</fieldset>
</div>
{{-- CODE_EAN --}}
{{-- EAN_CODE --}}
<div class="col-12 col-md-6 mb-3">
<fieldset class="container-fluid border p-3">
<legend>{{ Str::ucfirst(__('validation.custom.ean_code')) }}</legend>
@ -115,7 +115,7 @@
'label' => __(':field', [
'field' => Str::ucfirst(__('validation.custom.ean_code')),
]),
'name' => 'code_ean',
'name' => 'ean_code',
'required' => true,
'model' => $productModel ?? null,
'helper' => __('validation.ean', [

View file

@ -20,7 +20,7 @@
@include('back::modules.table-col-sorter', [
'cols' => [
'name' => Str::ucfirst(__('validation.attributes.name')),
'code_ean' => Str::ucfirst(__('validation.custom.ean_code')),
'ean_code' => Str::ucfirst(__('validation.custom.ean_code')),
'range_id' => Str::ucfirst(__('models.classes.range')),
],
])
@ -29,7 +29,7 @@
@foreach ($productModels as $productModel)
<tr class="border-bottom list-item">
<td class="text-center align-middle">{{ $productModel->name }}</td>
<td class="text-center align-middle">{{ $productModel->code_ean }}</td>
<td class="text-center align-middle">{{ $productModel->ean_code }}</td>
<td class="text-center align-middle">{{ $productModel->range->name }}</td>
<td class="text-end align-middle">
<form class="btn-group confirmDeleteTS" action="{{ route('bo.products.destroy', $productModel) }}" method="POST"

View file

@ -41,7 +41,7 @@
</div>
<div class="d-flex justify-content-center flex-column flex-sm-row mb-3">
<span class="h3 me-2">{{ Str::ucfirst(__('validation.custom.ean_code')) }}</span>
<span class="h3 fw-bold">{{ $productModel->code_ean }}</span>
<span class="h3 fw-bold">{{ $productModel->ean_code }}</span>
</div>
<div class="d-flex justify-content-center flex-column flex-sm-row mb-3">
<span class="h3 me-2">{{ Str::ucfirst(__('models.classes.range')) }}</span>

View file

@ -17,7 +17,7 @@
'email' => __('Email'),
];
if ($settings->use_ean_codes === 1) {
$cols['code_ean_id'] = __('Code EAN');
$cols['ean_code_id'] = __('Code EAN');
}
$cols['code_unique_id'] = __('Code unique');
if ($settings->winning_proof === 1) {

View file

@ -10,7 +10,7 @@
--}}
<div class="form-check form-switch ms-2">
<label class="col-form-label p-0" type="button" for="{{ $id }}"><b>{{ $label }}</b></label>
<label class="col-form-label p-0" for="{{ $id }}"><b>{{ $label }}</b></label>
<input
id="{{ $id }}"
aria-describedby="{{ $id }}Help"