From 0d648778aeffff5dad2ad2b13d4bd3e5da6629fd Mon Sep 17 00:00:00 2001 From: Noah Lancien Date: Tue, 5 Aug 2025 14:47:02 +0200 Subject: [PATCH 1/8] feat: take all VB commits from old git repo --- .composer/.htaccess | 1 + app/Campagne.php | 44 ++- app/Coupon.php | 72 ++-- app/Enums/CampaignTypes.php | 39 ++ app/Enums/Countries.php | 32 ++ app/Enums/UserRoles.php | 32 ++ app/Http/Controllers/AjaxController.php | 358 +----------------- .../Controllers/Api/v1/CouponsController.php | 3 +- .../Controllers/Api/v1/CustomerController.php | 151 ++++++++ app/Http/Controllers/Api/v1/MurController.php | 3 +- .../Controllers/BrandChangeController.php | 22 ++ app/Http/Controllers/CampagneController.php | 272 ++++++++----- .../Controllers/ChangeMarqueController.php | 24 -- app/Http/Controllers/ContactController.php | 259 +++++++------ app/Http/Controllers/CouponController.php | 174 +++------ app/Http/Controllers/CustomersController.php | 33 ++ app/Http/Controllers/ExportController.php | 15 +- app/Http/Controllers/HomeController.php | 35 -- .../Controllers/MaintenanceController.php | 107 ++---- app/Http/Controllers/MouvementController.php | 28 -- app/Http/Controllers/MurController.php | 29 +- app/Http/Controllers/PanierController.php | 56 +-- .../Controllers/StatistiqueController.php | 30 +- .../BrandChange/InvokeBrandChange.php | 32 ++ .../Requests/Campaign/AddCouponsCampaign.php | 32 ++ app/Http/Requests/Campaign/DeleteCampaign.php | 32 ++ .../Campaign/RemoveCouponCampaign.php | 32 ++ .../Requests/Campaign/RemoveNotifCampaign.php | 32 ++ .../Requests/Campaign/ShowDemoCampaign.php | 32 ++ app/Http/Requests/Campaign/StoreCampaign.php | 35 ++ .../Requests/Campaign/StoreNotifCampaign.php | 39 ++ app/Http/Requests/Campaign/UpdateCampaign.php | 35 ++ .../Requests/Contact/LoadWidgetContact.php | 32 ++ app/Http/Requests/Contact/SearchContact.php | 38 ++ app/Http/Requests/Contact/StoreContact.php | 38 ++ app/Http/Requests/Contact/UpdateContact.php | 42 ++ app/Http/Requests/Coupons/StoreCoupons.php | 75 ++++ app/Http/Requests/Coupons/UpdateCoupons.php | 82 ++++ app/Http/Requests/Customers/BasketRequest.php | 45 +++ .../Requests/Customers/PrintBasketRequest.php | 52 +++ .../Requests/Customers/SearchCustomers.php | 33 ++ app/Http/Requests/Export/PDFExport.php | 34 ++ app/Http/Requests/Users/StoreUsers.php | 36 ++ app/Http/Requests/Users/UpdateUsers.php | 39 ++ app/Http/Resources/CouponsResource.php | 9 +- app/Models/Basket.php | 41 ++ app/Models/BasketContent.php | 31 ++ app/Models/Customers.php | 9 + app/Notification.php | 2 + app/Providers/AuthServiceProvider.php | 2 +- app/Traits/CouponTrait.php | 90 ++--- app/Traits/GenerateBR.php | 93 ++--- composer.json | 3 +- config/app.php | 3 + package.json | 3 + resources/assets/images/coupons/mask.png | Bin 71844 -> 69073 bytes resources/js/ajax.js | 56 ++- resources/views/campagne/create.blade.php | 33 -- resources/views/campagne/edit.blade.php | 117 ------ resources/views/campagnes/create.blade.php | 55 +++ resources/views/campagnes/edit.blade.php | 147 +++++++ .../{campagne => campagnes}/index.blade.php | 50 ++- resources/views/contact/create.blade.php | 50 +-- resources/views/contact/edit.blade.php | 120 +++--- resources/views/contact/index.blade.php | 76 ++-- resources/views/coupon/create.blade.php | 170 +++++---- resources/views/coupon/edit.blade.php | 183 ++++----- resources/views/coupon/index.blade.php | 16 +- resources/views/customers/index.blade.php | 81 ++++ resources/views/export/index.blade.php | 33 +- resources/views/export/template.blade.php | 24 +- resources/views/layouts/header.blade.php | 14 +- resources/views/layouts/menu.blade.php | 1 + resources/views/maintenance/index.blade.php | 235 ++---------- resources/views/stats/detail.blade.php | 2 +- resources/views/stats/index.blade.php | 11 +- resources/views/user/create.blade.php | 74 ++-- resources/views/user/edit.blade.php | 39 +- routes/api.php | 2 + routes/web.php | 53 +-- 80 files changed, 2704 insertions(+), 1890 deletions(-) create mode 100644 .composer/.htaccess create mode 100644 app/Enums/CampaignTypes.php create mode 100644 app/Enums/Countries.php create mode 100644 app/Enums/UserRoles.php create mode 100644 app/Http/Controllers/BrandChangeController.php delete mode 100644 app/Http/Controllers/ChangeMarqueController.php create mode 100644 app/Http/Controllers/CustomersController.php delete mode 100644 app/Http/Controllers/HomeController.php delete mode 100644 app/Http/Controllers/MouvementController.php create mode 100644 app/Http/Requests/BrandChange/InvokeBrandChange.php create mode 100644 app/Http/Requests/Campaign/AddCouponsCampaign.php create mode 100644 app/Http/Requests/Campaign/DeleteCampaign.php create mode 100644 app/Http/Requests/Campaign/RemoveCouponCampaign.php create mode 100644 app/Http/Requests/Campaign/RemoveNotifCampaign.php create mode 100644 app/Http/Requests/Campaign/ShowDemoCampaign.php create mode 100644 app/Http/Requests/Campaign/StoreCampaign.php create mode 100644 app/Http/Requests/Campaign/StoreNotifCampaign.php create mode 100644 app/Http/Requests/Campaign/UpdateCampaign.php create mode 100644 app/Http/Requests/Contact/LoadWidgetContact.php create mode 100644 app/Http/Requests/Contact/SearchContact.php create mode 100644 app/Http/Requests/Contact/StoreContact.php create mode 100644 app/Http/Requests/Contact/UpdateContact.php create mode 100644 app/Http/Requests/Coupons/StoreCoupons.php create mode 100644 app/Http/Requests/Coupons/UpdateCoupons.php create mode 100644 app/Http/Requests/Customers/BasketRequest.php create mode 100644 app/Http/Requests/Customers/PrintBasketRequest.php create mode 100644 app/Http/Requests/Customers/SearchCustomers.php create mode 100644 app/Http/Requests/Export/PDFExport.php create mode 100644 app/Http/Requests/Users/StoreUsers.php create mode 100644 app/Http/Requests/Users/UpdateUsers.php create mode 100644 app/Models/Basket.php create mode 100644 app/Models/BasketContent.php delete mode 100644 resources/views/campagne/create.blade.php delete mode 100644 resources/views/campagne/edit.blade.php create mode 100644 resources/views/campagnes/create.blade.php create mode 100644 resources/views/campagnes/edit.blade.php rename resources/views/{campagne => campagnes}/index.blade.php (57%) create mode 100644 resources/views/customers/index.blade.php diff --git a/.composer/.htaccess b/.composer/.htaccess new file mode 100644 index 0000000..14249c5 --- /dev/null +++ b/.composer/.htaccess @@ -0,0 +1 @@ +Deny from all \ No newline at end of file diff --git a/app/Campagne.php b/app/Campagne.php index 8c127f1..6837a45 100644 --- a/app/Campagne.php +++ b/app/Campagne.php @@ -2,12 +2,33 @@ namespace App; +use App\Enums\CampaignTypes; +use App\Models\Basket; +use App\Models\PrintedWalls; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; +use Kwaadpepper\Enum\Traits\CastsEnums; class Campagne extends Model { use SoftDeletes; + use CastsEnums; + + public $enumCasts = [ + 'camp_type' => CampaignTypes::class + ]; + + /** + * @var array + */ + protected $fillable = [ + 'camp_siteId', + 'camp_name', + 'camp_type', + 'camp_limitedPrinting', + 'camp_published' + ]; /** * @var string @@ -86,13 +107,20 @@ class Campagne extends Model } /** - * @var array + * printedWalls relation + * @return HasMany */ - protected $fillable = [ - 'camp_siteId', - 'camp_name', - 'camp_type', - 'camp_limitedPrinting', - 'camp_published' - ]; + public function printedWalls() + { + return $this->hasMany(PrintedWalls::class, 'camp_id'); + } + + /** + * Basket model relation + * @return HasMany + */ + public function baskets() + { + return $this->hasMany(Basket::class); + } } diff --git a/app/Coupon.php b/app/Coupon.php index cab9d0b..fb7ef67 100644 --- a/app/Coupon.php +++ b/app/Coupon.php @@ -2,6 +2,7 @@ namespace App; +use App\Models\BasketContent; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; @@ -34,6 +35,40 @@ class Coupon extends Model */ protected $primaryKey = 'coupon_id'; + /** + * @var array + */ + protected $fillable = [ + 'coupon_code', + 'coupon_norme', + 'coupon_gs1code', + 'coupon_name', + 'coupon_value', + 'coupon_enablelimitquantity', + 'coupon_limitquantity', + 'coupon_enablelimitperiod', + 'coupon_limitperiod', + 'coupon_validity', + 'coupon_mindatediffusion', + 'coupon_maxdatediffusion', + 'coupon_maxdatevalidity', + 'coupon_thumbnail1', + 'coupon_thumbnail2', + 'coupon_thumbnail3', + 'coupon_thumbnail4', + 'coupon_image', + 'coupon_published', + 'coupon_logo_image', + 'coupon_rcs', + 'coupon_decoration_image', + 'coupon_validity_text', + 'coupon_site_contact', + 'coupon_legal_mentions', + 'coupon_legal_mentions_splitted', + 'coupon_seuil_alert', + 'coupon_treatment_center' + ]; + /** * OneToMany * Coupon -> Calendrier @@ -88,36 +123,11 @@ class Coupon extends Model } /** - * @var array + * BasketContent model relation + * @return HasMany */ - protected $fillable = [ - 'coupon_code', - 'coupon_norme', - 'coupon_gs1code', - 'coupon_name', - 'coupon_value', - 'coupon_enablelimitquantity', - 'coupon_limitquantity', - 'coupon_enablelimitperiod', - 'coupon_limitperiod', - 'coupon_validity', - 'coupon_mindatediffusion', - 'coupon_maxdatediffusion', - 'coupon_maxdatevalidity', - 'coupon_thumbnail1', - 'coupon_thumbnail2', - 'coupon_thumbnail3', - 'coupon_thumbnail4', - 'coupon_image', - 'coupon_published', - 'coupon_logo_image', - 'coupon_rcs', - 'coupon_decoration_image', - 'coupon_validity_text', - 'coupon_site_contact', - 'coupon_legal_mentions', - 'coupon_legal_mentions_splitted', - 'coupon_seuil_alert', - 'coupon_treatment_center' - ]; + public function basketContents() + { + return $this->hasMany(BasketContent::class); + } } diff --git a/app/Enums/CampaignTypes.php b/app/Enums/CampaignTypes.php new file mode 100644 index 0000000..3c05ef7 --- /dev/null +++ b/app/Enums/CampaignTypes.php @@ -0,0 +1,39 @@ + 0, + 'panier' => 1, + 'murss' => 2, + 'paniers' => 3, + ]; + } + + protected static function labels(): array + { + return [ + 'mur' => trans('Mur'), + 'panier' => trans('Panier'), + 'murss' => trans('Mur sans sélection'), + 'paniers' => trans('Panier Sélectionnable'), + ]; + } +} diff --git a/app/Enums/Countries.php b/app/Enums/Countries.php new file mode 100644 index 0000000..276ea17 --- /dev/null +++ b/app/Enums/Countries.php @@ -0,0 +1,32 @@ + 250, + 'belgique' => 56, + 'luxembourg' => 442, + ]; + } + + protected static function labels(): array + { + return [ + 'france' => 'France', + 'belgique' => 'Belgique', + 'luxembourg' => 'Luxembourg', + ]; + } +} diff --git a/app/Enums/UserRoles.php b/app/Enums/UserRoles.php new file mode 100644 index 0000000..7eee8cd --- /dev/null +++ b/app/Enums/UserRoles.php @@ -0,0 +1,32 @@ + 'superadmin', + 'admin' => 'admin', + 'marketing' => 'marketing' + ]; + } + + protected static function labels(): array + { + return [ + 'superadmin' => trans('Super-Admin'), + 'admin' => trans('Admin'), + 'marketing' => trans('Marketing') + ]; + } +} diff --git a/app/Http/Controllers/AjaxController.php b/app/Http/Controllers/AjaxController.php index 5b814ad..d2597d3 100644 --- a/app/Http/Controllers/AjaxController.php +++ b/app/Http/Controllers/AjaxController.php @@ -2,10 +2,8 @@ namespace App\Http\Controllers; -use App\Campagne; use App\Coupon; -use App\Contact; -use App\Panier; +use App\Enums\CampaignTypes; use App\PanierContent; use App\Statistique; use App\Mur; @@ -13,42 +11,14 @@ use App\Log; use Illuminate\Http\Request; use App\Traits\GenerateBR; use App\Traits\CouponTrait; -use App\Http\Controllers; -use Carbon\Carbon; class AjaxController extends Controller { use GenerateBR; use CouponTrait; - // ---- Ajoute des coupons à une campagne - public function add_coupons_ajax(Request $request) - { - $camp_id = $request->input('campid'); - $campagne = Campagne::where('camp_id', $camp_id)->first(); - $ids = $request->input('ids'); - - $coupons = Coupon::find($ids); - $campagne->coupons()->attach($coupons); - - return json_encode($coupons); - } - - // ---- Efface des coupons d'une campagne - public function remove_coupons_ajax(Request $request) - { - $camp_id = $request->input('campid'); - $campagne = Campagne::where('camp_id', $camp_id)->first(); - $coupon_id = $request->input('couponid'); - $coupon = Coupon::find($coupon_id); - - $campagne->coupons()->detach($coupon_id); - - return json_encode($coupon); - } - // ---- Dépublie un coupon - public function unpublish_coupons_ajax(Request $request) + public function unpublishCouponsAjax(Request $request) { $coupon_id = $request->input('couponid'); $coupon = Coupon::find($coupon_id); @@ -59,7 +29,7 @@ class AjaxController extends Controller } // ---- Publie un coupon - public function publish_coupons_ajax(Request $request) + public function publishCouponsAjax(Request $request) { $coupon_id = $request->input('couponid'); $coupon = Coupon::find($coupon_id); @@ -69,31 +39,8 @@ class AjaxController extends Controller return json_encode($coupon); } - // ---- Dépublie une campagne - public function unpublish_camp_ajax(Request $request) - { - $campid = $request->input('campid'); - $campagne = Campagne::find($campid); - $campagne->camp_published = 0; - $campagne->save(); - - return json_encode($campagne); - } - - // ---- Publie une campagne - public function publish_camp_ajax(Request $request) - { - $campid = $request->input('campid'); - $campagne = Campagne::find($campid); - $campagne->camp_published = 1; - $campagne->save(); - - return json_encode($campagne); - } - - // ---- Impression des BR - public function impression_ajax(Request $request) + public function impressionAjax(Request $request) { if ($request->session()->has('coupons')) { @@ -132,7 +79,7 @@ class AjaxController extends Controller $coupons = ''; $coupons .= ''; html += ''; @@ -34,7 +33,7 @@ $('#addCoupons').on('click', function(event){ $('#addCoupons').parent().find('.inputGroup input').each(function(event){ if($(this).val() == coupon.coupon_id){ $(this).parent('.inputGroup').remove(); - } + } }) $.fancybox.close(); }); @@ -43,7 +42,7 @@ $('#addCoupons').on('click', function(event){ console.log(e.responseText); } }); -}) +}) @@ -59,21 +58,18 @@ $('body').on('click', '.delete-coupon', function(event){ callback : function (value) { if (value) { event.preventDefault(); - + $.ajaxSetup({ headers: { 'X-CSRF-TOKEN': $('#csrf').val() } }); $.ajax({ - url: '/remove-coupons-ajax', + url: '/bo/campagne/'+ campid +'/remove-coupon/' + couponid, type: 'POST', - data: { - couponid: couponid, - campid: campid - }, + data: {}, dataType: 'JSON', - success: function (reponse) { + success: function (reponse) { trLinked.remove(); var html = "
"; html += ""; @@ -87,7 +83,7 @@ $('body').on('click', '.delete-coupon', function(event){ }); } } - }); + }); }) @@ -114,14 +110,14 @@ $('body').on('click', '.unpublish', function(event){ couponid: couponid }, dataType: 'JSON', - success: function (reponse) { + success: function (reponse) { element.empty().html('') } }); } } }); - + }) $('body').on('click', '.publish', function(event){ @@ -147,14 +143,14 @@ $('body').on('click', '.publish', function(event){ couponid: couponid }, dataType: 'JSON', - success: function (reponse) { + success: function (reponse) { element.empty().html('') } }); } } }); - + }) @@ -175,20 +171,18 @@ $('body').on('click', '.unpublishcamp', function(event){ } }); $.ajax({ - url: '/unpublish-camp-ajax', + url: '/bo/campagne/'+ campid +'/toggle-publish/0', type: 'POST', - data: { - campid: campid - }, + data: {}, dataType: 'JSON', - success: function (reponse) { + success: function (reponse) { element.empty().html('') } }); } } }); - + }) $('body').on('click', '.publishcamp', function(event){ @@ -208,18 +202,16 @@ $('body').on('click', '.publishcamp', function(event){ } }); $.ajax({ - url: '/publish-camp-ajax', + url: '/bo/campagne/'+ campid +'/toggle-publish/1', type: 'POST', - data: { - campid: campid - }, + data: {}, dataType: 'JSON', - success: function (reponse) { + success: function (reponse) { element.empty().html('') } }); } } }); - -}) \ No newline at end of file + +}) diff --git a/resources/views/campagne/create.blade.php b/resources/views/campagne/create.blade.php deleted file mode 100644 index b3e8ef3..0000000 --- a/resources/views/campagne/create.blade.php +++ /dev/null @@ -1,33 +0,0 @@ -@extends('layouts.main') - -@section('content') -

Nouvelle campagne

-
- {!! Form::open(['route' => 'campagne.store']) !!} -
-
- {!! Form::label('camp_name', 'Nom') !!} - {!! Form::input('text', 'camp_name', null, ['required']) !!} -
-
- {!! Form::label('camp_type', 'Type') !!} - {!! Form::select('camp_type', ['Mur', 'Panier', 'Mur sans sélection', 'Panier Sélectionnable']) !!} -
-
-
-
- {!! Form::label('camp_published', 'Publier') !!} - {!! Form::select('camp_published', ['Non', 'Oui']) !!} -
-
- {!! Form::label('camp_limitedPrinting', 'Limiter les impressions à 1 BR à la fois') !!} - {!! Form::checkbox('camp_limitedPrinting', 1, false, array('id'=>'camp_limitedPrinting')) !!} - {!! Form::label('camp_limitedPrinting', '') !!} -
-
-
- {!! Form::submit('Enregistrer') !!} -
- {!! Form::close() !!} -
-@endsection diff --git a/resources/views/campagne/edit.blade.php b/resources/views/campagne/edit.blade.php deleted file mode 100644 index 343f568..0000000 --- a/resources/views/campagne/edit.blade.php +++ /dev/null @@ -1,117 +0,0 @@ -@extends('layouts.main') - -@section('content') -

Editer la campagne

-
- {!! Form::model($campagne, ['route' => ['campagne.update', $campagne]]) !!} - @method('PUT') -
-
- {!! Form::label('camp_name', 'Nom') !!} - {!! Form::input('text', 'camp_name', null, ['required']) !!} -
-
- {!! Form::label('camp_type', 'Type') !!} - {!! Form::select('camp_type', ['Mur', 'Panier', 'Mur sans sélection', 'Panier Sélectionnable']) !!} -
-
-
-
- {!! Form::label('camp_published', 'Publier') !!} - {!! Form::select('camp_published', ['Non', 'Oui']) !!} -
-
- {!! Form::label('camp_limitedPrinting', 'Limiter les impressions à 1 BR à la fois') !!} - {!! Form::checkbox('camp_limitedPrinting', 1, ($campagne->camp_limitedPrinting ==1?true:null), array('id'=>'camp_limitedPrinting')) !!} - {!! Form::label('camp_limitedPrinting', '') !!} -
-
-
- {!! Form::submit('Mettre à jour') !!} -
- {!! Form::close() !!} -
-
-

Coupons de la campagne

- -
'; - if ($campagne->camp_type == 0) { + if ($campagne->camp_type->equals(CampaignTypes::mur())) { // ---- POUR LES MURS if (!isset($couponsToPrint)) { die(); @@ -167,7 +114,7 @@ class AjaxController extends Controller $stat->save(); }//end if }//end foreach - } elseif ($campagne->camp_type == 1) { + } elseif ($campagne->camp_type->equals(CampaignTypes::panier())) { // ---- POUR LES PANIERS if (!isset($panier)) { die(); @@ -235,7 +182,7 @@ class AjaxController extends Controller $panier->panier_pending = 1; } $panier->save(); - } elseif ($campagne->camp_type == 2) { + } elseif ($campagne->camp_type->equals(CampaignTypes::murss())) { // ---- POUR LES MURS SANS SELECTION $nbCoupon = 0; $cps = $campagne->coupons()->get(); @@ -266,7 +213,7 @@ class AjaxController extends Controller $stat->save(); }//end if }//end foreach - } elseif ($campagne->camp_type == 3) { + } elseif ($campagne->camp_type->equals(CampaignTypes::paniers())) { // ---- POUR LES PANIERS AVEC SELECTION if (!isset($couponsToPrint)) { die(); @@ -339,12 +286,12 @@ class AjaxController extends Controller $contact = $request->session()->get('contact'); } - if ($campagne->camp_type == 0 || $campagne->camp_type == 2) { + if ($campagne->camp_type->equals(CampaignTypes::mur()) || $campagne->camp_type->equals(CampaignTypes::murss())) { if (!isset($contact)) { die(); } return redirect(url('/') . '/?refsite=' . $campagne->camp_siteId . '&refcamp=' . $campagne->camp_id . '&hash=' . $contact->contact_hash); - } elseif ($campagne->camp_type == 1 || $campagne->camp_type == 3) { + } elseif ($campagne->camp_type->equals(CampaignTypes::panier()) || $campagne->camp_type->equals(CampaignTypes::paniers())) { if ($request->session()->has('panier')) { $panier = $request->session()->get('panier'); } else { @@ -353,289 +300,4 @@ class AjaxController extends Controller return redirect(url('/') . '/?basketid=' . $panier->panier_ref); } } - - // ---- Charge les widget d'assistance - public function load_widget(Request $request) - { - if (isset($request->camp_id)) { - $camp_id = $request->camp_id; - } else { - die; - } - - if (isset($request->contact_hash)) { - $contact_hash = $request->contact_hash; - } else { - die; - } - - $campagne = Campagne::where(['camp_id' => $camp_id])->first(); - $contact = Contact::where(['contact_hash' => $contact_hash])->first(); - $coupons = $campagne->coupons; - - foreach ($coupons as $key => $coupon) { - if (!$this->isAvailableToPrint($coupon, $campagne)) { - $coupons->forget($key); - } - } - - $allLogs = Log::where(['log_refsite' => $campagne->camp_siteId, 'log_contactId' => $contact->contact_id, 'log_statut' => 'OK'])->orderBy('created_at', 'DESC')->get(); - $logs = array(); - foreach ($allLogs as &$l) { - $params = json_decode($l->log_params); - if ($l->log_action == 3 || $l->log_action == 1) { - $l->log_message = json_decode($l->log_message, true); - } - if (intval($params->refcamp) == $camp_id) { - array_push($logs, $l); - } - } - - if ($campagne->camp_type == 0 || $campagne->camp_type == 2) { -// ---- Pour les types murs - foreach ($coupons as &$coupon) { - $coupon->isAvailable = $this->isAvailable($coupon, $campagne, $contact); - } - unset($coupon); - - // Montant total et Nombre réductions disponible - $montantDispo = 0; - $montantTotal = 0; - $nbCoupon = 0; - foreach ($coupons as $coupon) { - $montantTotal += $coupon->coupon_value; - if ($coupon->isAvailable == true) { - $montantDispo += $coupon->coupon_value; - $nbCoupon++; - } - } - - if (sizeof($coupons) == $nbCoupon) { - $reset = false; - } else { - $reset = true; - } - - $campagne->url = url('/') . "/?refsite=" . $campagne->camp_siteId . "&refcamp=" . $camp_id . "&hash=" . $contact->contact_hash; - - $returnHTML = view('widgets.mur') - ->with([ - 'campagne' => $campagne, - 'reset' => $reset, - 'montantTotal' => $montantTotal, - 'montantDispo' => $montantDispo, - 'logs' => $logs, - 'contact' => $contact]) - ->renderSections()['widget']; - - return json_encode(array('success' => true, 'html' => $returnHTML)); - } else { -// ---- Pour les types paniers - $panier = Panier::where(['panier_campId' => $campagne->camp_id, 'panier_contactId' => $contact->contact_id, 'panier_pending' => 1])->first(); - if ($panier) { - $panier_ref = $panier->panier_ref; - foreach ($coupons as &$coupon) { - $paniercontent = PanierContent::where(['panier_content_couponId' => $coupon->coupon_id, 'panier_content_panierId' => $panier->panier_id])->first(); - if ($paniercontent) { - $coupon->qty = $paniercontent->panier_content_quantity; - } else { - $coupon->qty = 0; - } - } - $campagne->url = url('/') . "/?basketid=" . $panier_ref; - unset($coupon); - } else { - $panier_ref = ""; - foreach ($coupons as &$coupon) { - $coupon->qty = 0; - } - $campagne->url = "Pas d'url disponible (panier non créé)"; - } - - $returnHTML = view('widgets.panier') - ->with([ - 'campagne' => $campagne, - 'contact' => $contact, - 'panier' => $panier_ref, - 'logs' => $logs, - 'coupons' => $coupons - ]) - ->renderSections()['widget']; - return json_encode(array('success' => true, 'html' => $returnHTML)); - }//end if - } - - // ---- Montre les campagnes avec un compte de démo - public function show_demo(Request $request) - { - if (isset($request->camp_id)) { - $camp_id = $request->camp_id; - } else { - die; - } - - $campagne = Campagne::where(['camp_id' => $camp_id])->first(); - $siteId = $campagne->camp_siteId; - $demoContact = Contact::where(['contact_siteId' => $siteId, 'contact_ref' => 'DEMO'])->first(); - - if (!isset($demoContact)) { - $req = new \Illuminate\Http\Request(); - $req->setMethod('POST'); - $req->request->add([ - 'refsite' => $siteId, - 'refadherent' => 'DEMO', - 'prenom' => 'Compte', - 'nom' => 'Démonstration', - 'email' => 'demo@demo.fr', - 'code_postal' => '72000', - 'commune' => 'Le Mans', - 'pays' => 250 - ]); - - $contact = new Controllers\ContactController(); - $res = $contact->updateContact($req); - $demoContact = Contact::where(['contact_siteId' => $siteId, 'contact_ref' => 'DEMO'])->first(); - } - - if ($campagne->camp_type == 0 || $campagne->camp_type == 2) { - // ---- DEMONSTRATION POUR LES MURS - $myRequest = new \Illuminate\Http\Request(); - $myRequest->setMethod('POST'); - $myRequest->request->add(['refcamp' => $camp_id, 'refadherent' => 'DEMO', 'admin' => 1]); - - // ---- EFFACER LES CONTROLES SI EXISTANT - $mur = new Controllers\MurController(); - $res = $mur->deleteControlContact($myRequest); - - // ---- RETOURNE l'URL - $url = "/?refsite=" . $siteId . "&refcamp=" . $camp_id . "&hash=" . $demoContact->contact_hash; - return json_encode($url); - } else { - // ---- DEMONSTRATION POUR LES PANIERS - $panier = Panier::where(['panier_campId' => $camp_id, 'panier_contactId' => $demoContact->contact_id, 'panier_pending' => 1])->first(); - - if ($panier) { - $url = "/?basketid=" . $panier->panier_ref; - } else { - // --- Pas de panier Création d'un nouveau - // --- Récuperation des coupons de la campagne - $coupons = $campagne->coupons()->get(); - - foreach ($coupons as $key => $coupon) { - if (!$this->isAvailableToPrint($coupon, $campagne)) { - $coupons->forget($key); - } - } - - $couponsTotal = sizeof($coupons); - $couponsAdded = 0; - $basketContent = ""; - - foreach ($coupons as $coupon) { - $basketContent .= "(1)" . $coupon->coupon_code; - $couponsAdded++; - if ($couponsAdded != $couponsTotal) { - $basketContent .= ","; - } - } - // --- création de l'objet request - $req = new \Illuminate\Http\Request(); - $req->setMethod('POST'); - $req->request->add([ - 'refsite' => $siteId, - 'refcamp' => $camp_id, - 'refadherent' => $demoContact->contact_ref, - 'nom' => $demoContact->contact_nom, - 'prenom' => $demoContact->contact_prenom, - 'email' => $demoContact->contact_email, - 'code_postal' => $demoContact->contact_cp, - 'commune' => $demoContact->contact_ville, - 'pays' => $demoContact->contact_country, - 'panier' => $basketContent, - 'admin' => 1 - ]); - $panier = new Controllers\PanierController(); - $res = $panier->updateBasket($req); - $url = "/?basketid=" . $res->original['Panier']->Panier; - }//end if - return json_encode($url); - }//end if - } - - // ---- MAINTENANCE : CLEAN DE LA BDD - // ---- Fonction de récap des contacts que l'on va effacer - public function get_clean_contacts_detail(Request $request) - { - $contacts = Contact::where('contact_updatedate', '<', Carbon::now()->subMonth())->get(); - $date = Carbon::now(); - $contact_count = 0; - foreach ($contacts as $contact) { - $paniers = $contact->paniers()->get(); - $canbeDeleted = true; - foreach ($paniers as $panier) { - if ($panier->panier_pending == 1) { - $canbeDeleted = false; - } - } - if ($canbeDeleted) { - $contact_count++; - // $testcontact .= $contact->contact_id.", "; - } - } - return json_encode(['date' => $date, 'contact_count' => $contact_count]); - } - - // ---- Fonction de nettoyage de la base contact - public function clean_contacts(Request $request) - { - $contacts = Contact::where('contact_updatedate', '<', Carbon::now()->subMonth())->get(); - $testcontact = ""; - foreach ($contacts as $contact) { - $paniers = $contact->paniers()->get(); - $canbeDeleted = true; - foreach ($paniers as $panier) { - if ($panier->panier_pending == 1) { - $canbeDeleted = false; - } - } - $murs = $contact->murs()->get(); - if (isset($murs) && !empty($murs)) { - $canbeDeleted = false; - } - if ($canbeDeleted) { - $contact->delete(); - $testcontact .= $contact->contact_id . ", "; - } - } - return json_encode($testcontact); - } - - // ---- Fonction de nettoyage de la base mur - public function clean_walls(Request $request) - { - $murs = Mur::where('mur_createdate', '<', Carbon::now()->subYear())->where(['mur_deleted' => 1])->get(); - $testmur = ""; - foreach ($murs as $mur) { - $mur->delete(); - $testmur .= $mur->mur_id . ", "; - } - return json_encode($testmur); - } - - // ---- Fonction de nettoyage de la base panier et panierContent - public function clean_baskets(Request $request) - { - $paniers = Panier::where('panier_createdate', '<', Carbon::now()->subMonth(2))->where(['panier_pending' => 0])->get(); - $testpanier = ""; - foreach ($paniers as $panier) { - $testpanier .= $panier->panier_id . ": "; - $panierContents = $panier->panierContents()->get(); - foreach ($panierContents as $panierContent) { - $testpanier .= $panierContent->panier_content_id . ', '; - $panierContent->delete(); - } - $panier->delete(); - } - return json_encode($testpanier); - } } diff --git a/app/Http/Controllers/Api/v1/CouponsController.php b/app/Http/Controllers/Api/v1/CouponsController.php index 01b0dee..1b9852f 100644 --- a/app/Http/Controllers/Api/v1/CouponsController.php +++ b/app/Http/Controllers/Api/v1/CouponsController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\v1; use App\Campagne; use App\Coupon; +use App\Enums\CampaignTypes; use App\Http\Controllers\Controller; use App\Http\Requests\Coupons\PrintRequest; use App\Http\Resources\StatusResource; @@ -58,7 +59,7 @@ class CouponsController extends Controller // TODO : Vérifier si les coupons sont dans la campagne // TODO : Logs $coupons = []; - if ($campagne->camp_type == 0) { + if ($campagne->camp_type->equals(CampaignTypes::mur())) { // ---- POUR LES MURS foreach ($couponsToPrint as $key => $coupon_code) { $cp = Coupon::where(['coupon_code' => $coupon_code])->first(); diff --git a/app/Http/Controllers/Api/v1/CustomerController.php b/app/Http/Controllers/Api/v1/CustomerController.php index d3e29cd..56d7b1f 100644 --- a/app/Http/Controllers/Api/v1/CustomerController.php +++ b/app/Http/Controllers/Api/v1/CustomerController.php @@ -4,20 +4,25 @@ namespace App\Http\Controllers\Api\v1; use App\Calendrier; use App\Campagne; +use App\Enums\CampaignTypes; use App\Http\Requests\Customers\StoreRequest; use App\Http\Resources\CustomersResource; use App\Http\Resources\StatusResource; use App\Models\Customers; use App\Http\Controllers\Controller; +use App\Http\Requests\Customers\BasketRequest; use App\Http\Requests\Customers\MurRequest; +use App\Http\Requests\Customers\PrintBasketRequest; use App\Http\Resources\CouponsResource; use App\Traits\CouponTrait; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Request; +use LDC\LDCCRM\CrmApiV2; class CustomerController extends Controller { use CouponTrait; + use GenerateBR; /** * Display a listing of the resource. @@ -107,12 +112,22 @@ class CustomerController extends Controller return new StatusResource(['status' => 'KO', 'code' => '501', 'message' => 'Not Implemented']); } + /** + * Recupere le contenu d'un mur pour une campagne et un user + * @param MurRequest $request + * @return StatusResource + * @throws ValidationException + */ public function mur(MurRequest $request) { $datas = $request->validated(); $customer = Customers::where('hashcode', $datas['hashcode'])->first(); $campagne = Campagne::find($datas['refcamp']); + + if (!$campagne->camp_type->equals(CampaignTypes::mur())) { + return new StatusResource(['status' => 'KO', 'code' => 404, 'message' => 'Campaign is not mur.']); + } if ($campagne->camp_published === 0) { return new StatusResource(['status' => 'KO', 'code' => 404, 'message' => 'Campaign not found.']); } @@ -158,4 +173,140 @@ class CustomerController extends Controller 'coupons' => CouponsResource::collection($coupons) ]); } + + /** + * Recupere le contenu d'un panier pour une campagne et un user + * @param BasketRequest $request + * @return StatusResource + * @throws ValidationException + */ + public function basket(BasketRequest $request) + { + $montantDispo = 0; + $montantTotal = 0; + $nbCoupons = 0; + $couponTotal = 0; + $coupons = []; + + $datas = $request->validated(); + + $customer = Customers::where('hashcode', $datas['hashcode'])->first(); + $campagne = Campagne::find($datas['refcamp']); + + if (!$campagne->camp_type->equals(CampaignTypes::panier())) { + return new StatusResource(['status' => 'KO', 'code' => 404, 'message' => 'Campaign is not basket.']); + } + if ($campagne->camp_published === 0) { + return new StatusResource(['status' => 'KO', 'code' => 404, 'message' => 'Campaign not found.']); + } + $basket = $customer->baskets()->where('campagne_id', $campagne->camp_id)->first(); + if (is_null($basket)) { + return new StatusResource(['status' => 'KO', 'code' => 404, 'message' => 'Basket not found.']); + } + + foreach ($basket->contents as $content) + { + $published = true; + $nbCoupons += $content->quantity; + $couponTotal += $content->printed; + $montantTotal += ($content->coupon->coupon_value * $content->printed); + $coupon = $content->coupon()->whereRaw("coupon_published = 1 AND (coupon_mindatediffusion IS NULL OR coupon_mindatediffusion <= NOW()) AND (coupon_maxdatediffusion IS NULL OR coupon_maxdatediffusion >= NOW()) AND (coupon_maxdatevalidity IS NULL OR coupon_maxdatevalidity >= NOW())")->first(); + if (!is_null($coupon)) + { + // ---- Contrôle des calendriers de diffusion + $calendars = Calendrier::where(['calendrier_couponId' => $coupon->coupon_id])->get(); + if (!$calendars->isEmpty()) { + $published = false; + foreach ($calendars as $calendar) { + if (strtotime(date('Y-m-d H:i:s')) > strtotime($calendar->calendrier_startdate) && strtotime(date('Y-m-d H:i:s')) < strtotime($calendar->calendrier_enddate)) { + $published = true; + } + } + } + if ($published){ + $montantDispo += ($coupon->coupon_value * $content->quantity); + $montantTotal += ($coupon->coupon_value * $content->quantity); + $couponTotal += $content->quantity; + $coupons = array_merge($coupons, [ + 'quantity' => $content->quantity, + 'coupon' => new CouponsResource($coupon) + ]); + } + } + } + + return new StatusResource([ + 'status' => "OK", + 'name' => $campagne->camp_name, + 'limited_printing' => $campagne->camp_limitedPrinting, + 'available_coupon' => $nbCoupons, + 'available_amount' => $montantDispo, + 'total_coupon' => $couponTotal, + 'total_amount' => $montantTotal, + 'coupons' => $coupons + ]); + } + + public function printBasket(PrintBasketRequest $request) + { + $datas = $request->validated(); + + $customer = Customers::where('hashcode', $datas['hashcode'])->first(); + $campagne = Campagne::find($datas['refcamp']); + + if (!$campagne->camp_type->equals(CampaignTypes::panier())) { + return new StatusResource(['status' => 'KO', 'code' => 404, 'message' => 'Campaign is not basket.']); + } + if ($campagne->camp_published === 0) { + return new StatusResource(['status' => 'KO', 'code' => 404, 'message' => 'Campaign not found.']); + } + if ($campagne->site->crm_token === null) { + return new StatusResource(['status' => 'KO', 'code' => 500, 'message' => 'CRM Token is missing for site_id : ' . $campagne->site->site_id]); + } + if ($campagne->site->crm_password === null) { + return new StatusResource(['status' => 'KO', 'code' => 500, 'message' => 'CRM Password is missing for site_id : ' . $campagne->site->site_id]); + } + $basket = $customer->baskets()->where('campagne_id', $campagne->camp_id)->first(); + if (is_null($basket)) { + return new StatusResource(['status' => 'KO', 'code' => 404, 'message' => 'Basket not found.']); + } + $crm = new CrmApiV2(config('crm.CRM_ENDPOINT'), $campagne->site->crm_token, $campagne->site->crm_password); + try { + /** + * @var \LDC\LDCCRM\Models\Customer + */ + $crmInfos = $crm->getCustomer($customer->hashcode); + } catch (\Exception $e) { + Log::critical($e->getMessage()); + return new StatusResource(['status' => 'KO', 'code' => 422, 'message' => 'Customer not found.']); + } + if (is_null($crmInfos)) { + return new StatusResource(['status' => 'KO', 'code' => 422, 'message' => 'Customer not found.']); + } + + foreach ($basket->contents as $content) + { + $published = true; + $coupon = $content->coupon()->whereRaw("coupon_published = 1 AND (coupon_mindatediffusion IS NULL OR coupon_mindatediffusion <= NOW()) AND (coupon_maxdatediffusion IS NULL OR coupon_maxdatediffusion >= NOW()) AND (coupon_maxdatevalidity IS NULL OR coupon_maxdatevalidity >= NOW())")->first(); + $b64img = $this->printCoupon($coupon, $customer, $crmInfos); + if (!is_null($coupon)) + { + // ---- Contrôle des calendriers de diffusion + $calendars = Calendrier::where(['calendrier_couponId' => $coupon->coupon_id])->get(); + if (!$calendars->isEmpty()) { + $published = false; + foreach ($calendars as $calendar) { + if (strtotime(date('Y-m-d H:i:s')) > strtotime($calendar->calendrier_startdate) && strtotime(date('Y-m-d H:i:s')) < strtotime($calendar->calendrier_enddate)) { + $published = true; + } + } + } + if ($published){ + // TODO : Print coupon + $b64img = $this->printCoupon($coupon, $customer, $crmInfos); + dd($b64img); + } + } + } + } } diff --git a/app/Http/Controllers/Api/v1/MurController.php b/app/Http/Controllers/Api/v1/MurController.php index b502130..4b19eec 100644 --- a/app/Http/Controllers/Api/v1/MurController.php +++ b/app/Http/Controllers/Api/v1/MurController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\v1; use App\Calendrier; use App\Campagne; +use App\Enums\CampaignTypes; use App\Http\Controllers\Controller; use App\Http\Requests\Murs\MursShowRequest; use App\Http\Resources\CouponsResource; @@ -22,7 +23,7 @@ class MurController extends Controller if ($campagne->camp_published === 0) { return new StatusResource(['status' => 'KO', 'code' => 404, 'message' => 'Campaign not found.']); } - if ($campagne->camp_type !== 0) { + if ($campagne->camp_type->equals(CampaignTypes::mur())) { return new StatusResource(['status' => 'KO', 'code' => 403, 'message' => 'Campaign type error.']); } diff --git a/app/Http/Controllers/BrandChangeController.php b/app/Http/Controllers/BrandChangeController.php new file mode 100644 index 0000000..7f0c307 --- /dev/null +++ b/app/Http/Controllers/BrandChangeController.php @@ -0,0 +1,22 @@ +site_id); + session()->put('site', $site); + return redirect()->back(); + } +} diff --git a/app/Http/Controllers/CampagneController.php b/app/Http/Controllers/CampagneController.php index 9fb08b8..9b052c2 100644 --- a/app/Http/Controllers/CampagneController.php +++ b/app/Http/Controllers/CampagneController.php @@ -6,17 +6,25 @@ use App\Campagne; use App\Site; use App\Coupon; use App\Contact; +use App\Enums\CampaignTypes; +use App\Http\Requests\Campaign\AddCouponsCampaign; +use App\Http\Requests\Campaign\DeleteCampaign; +use App\Http\Requests\Campaign\RemoveCouponCampaign; +use App\Http\Requests\Campaign\RemoveNotifCampaign; +use App\Http\Requests\Campaign\ShowDemoCampaign; +use App\Http\Requests\Campaign\StoreCampaign; +use App\Http\Requests\Campaign\StoreNotifCampaign; +use App\Http\Requests\Campaign\UpdateCampaign; use App\Mur; use App\PanierContent; use App\Notification; +use App\Panier; use Illuminate\Http\Request; use Illuminate\Support\Facades\File; use App\Traits\CouponTrait; -use Response; class CampagneController extends Controller { - // use BarcodeGenerator; use CouponTrait; /** @@ -26,8 +34,11 @@ class CampagneController extends Controller */ public function index() { - $campagnes = Campagne::where('camp_siteId', session()->get('site')->site_id)->get(); - return view('campagne.index')->with('campagnes', $campagnes); + // TODO : Revoir le passage du site id ? + $siteId = session()->get('site')->site_id; + + $campagnes = Campagne::where('camp_siteId', $siteId)->get(); + return view('campagnes.index')->with('campagnes', $campagnes); } /** @@ -37,38 +48,32 @@ class CampagneController extends Controller */ public function create() { - return view('campagne.create'); + return view('campagnes.create'); } /** * Store a newly created resource in storage. * - * @param \Illuminate\Http\Request $request + * @param StoreCampaign $request * @return \Illuminate\Http\Response */ - public function store(Request $request) + public function store(StoreCampaign $request) { - - $campagne = new Campagne(); - $campagne->camp_name = $request->input('camp_name'); - $campagne->camp_type = $request->input('camp_type'); - $campagne->camp_published = $request->input('camp_published'); - if (null !== $request->input('camp_limitedPrinting')) { - $campagne->camp_limitedPrinting = $request->input('camp_limitedPrinting'); - } - $campagne->camp_siteId = session()->get('site')->site_id; - + // TODO : Revoir le passage du site $site = session()->get('site'); + $campagne = new Campagne(); + $campagne->fill(array_merge($request->validated(), ['camp_siteId' => $site->site_id])); + $campagne->save(); + if ($site->campagnes()->save($campagne)) { - // File::makeDirectory(public_path('/landings/' . $campagne->camp_id)); - if ($campagne->camp_type == 0) { + if ($campagne->camp_type->equals(CampaignTypes::mur())) { File::copyDirectory(resource_path('/views/template/mur'), public_path('/landings/' . $campagne->camp_id)); - } elseif ($campagne->camp_type == 1) { + } elseif ($campagne->camp_type->equals(CampaignTypes::panier())) { File::copyDirectory(resource_path('/views/template/panier'), public_path('/landings/' . $campagne->camp_id)); - } elseif ($campagne->camp_type == 2) { + } elseif ($campagne->camp_type->equals(CampaignTypes::murss())) { File::copyDirectory(resource_path('/views/template/murnoselection'), public_path('/landings/' . $campagne->camp_id)); - } elseif ($campagne->camp_type == 3) { + } elseif ($campagne->camp_type->equals(CampaignTypes::paniers())) { File::copyDirectory(resource_path('/views/template/panierselection'), public_path('/landings/' . $campagne->camp_id)); } } @@ -84,45 +89,35 @@ class CampagneController extends Controller */ public function edit(Campagne $campagne) { - $site = Site::findOrFail(session()->get('site')->id)->first(); + // TODO : Revoir le passage du site id + $siteId = session()->get('site')->id; - $couponsLinked = $campagne->coupons()->get(); - $campid = $campagne->camp_id; + $couponsNotLinked = Coupon::whereDoesntHave('campagnes', function ($q) use ($campagne) { + $q->where('campagne_id', $campagne->camp_id); + })->where('coupon_siteId', $siteId)->where('coupon_published', 1)->get(); - $couponsNotLinked = Coupon::whereDoesntHave('campagnes', function ($q) use ($campid) { - $q->where('campagne_id', $campid); - })->where('coupon_siteId', session()->get('site')->id)->get(); - - return view('campagne.edit', ['campagne' => $campagne, 'coupons' => $couponsNotLinked, 'couponsLinked' => $couponsLinked]); + return view('campagnes.edit', ['campagne' => $campagne, 'coupons' => $couponsNotLinked]); } /** * Update the specified resource in storage. * - * @param \Illuminate\Http\Request $request + * @param UpdateCampaign $request * @param \App\Campagne $campagne * @return \Illuminate\Http\Response */ - public function update(Request $request, Campagne $campagne) + public function update(UpdateCampaign $request, Campagne $campagne) { - $site = Site::findOrFail(session()->get('site')->id)->first(); - - $couponsLinked = $campagne->coupons()->get(); - $campid = $campagne->camp_id; - - $couponsNotLinked = Coupon::whereDoesntHave('campagnes', function ($q) use ($campid) { - $q->where('campagne_id', $campid); - })->where('coupon_siteId', session()->get('site')->id)->get(); + $campagne->fill($request->validated()); if (!isset($request->camp_limitedPrinting)) { $campagne->camp_limitedPrinting = 0; - $campagne->save(); } - if ($campagne->update($request->all())) { - session()->flash('update_ok', 'La campagne a bien été modifiée'); + if ($campagne->save()) { + session()->flash('update_ok', __('La campagne a bien été modifiée')); } else { - session()->flash('update_nok', 'Erreur lors de l\'enregistrement'); + session()->flash('update_nok', __("Erreur lors de l'enregistrement")); } return redirect()->route('campagne.edit', ['campagne' => $campagne]); @@ -131,45 +126,60 @@ class CampagneController extends Controller /** * Remove the specified resource from storage. * + * @param DeleteCampaign $request * @param \App\Campagne $campagne * @return \Illuminate\Http\Response */ - public function destroy(Campagne $campagne) + public function destroy(DeleteCampaign $request, Campagne $campagne) { $campagne->camp_published = 0; $campagne->save(); - // File::deleteDirectory(public_path('/landings/' . $campagne->camp_id)); + if ($campagne->delete()) { return redirect(route('campagne.index')); } else { session()->flash('delete_nok', 'Erreur lors de la suppression'); - return view('campagne.edit')->with('campagne', $campagne); + return view('campagnes.edit')->with('campagne', $campagne); } } - /** - * @param \Illuminate\Http\Request $request - * - * @return array - */ - public function saveNotification(Request $request) + // ---- Ajoute des coupons à une campagne + public function addCoupons(AddCouponsCampaign $request, Campagne $campaign) { - $notification = new Notification(); - $notification->notif_name = $request->input('notif_name'); - $notification->notif_email = $request->input('notif_email'); - $notification->notif_campId = $request->input('notif_campId'); + $coupons = Coupon::find($request->ids); + $campaign->coupons()->attach($coupons); + return json_encode($coupons); + } + + // ---- Efface des coupons d'une campagne + public function removeCoupon(RemoveCouponCampaign $request, Campagne $campaign, Coupon $coupon) + { + $campaign->coupons()->detach($coupon); + return json_encode($coupon); + } + + /** + * @param \Illuminate\Http\Request $request + * + * @return array + */ + public function saveNotification(StoreNotifCampaign $request, Campagne $campaign) + { + $notification = new Notification(); + $notification->fill(array_merge($request->validated(), ['notif_campId' => $campaign->camp_id])); $notification->save(); + return redirect()->back(); } /** + * @param RemoveNotifCampaign $request * @param \App\Notification $notification * * @return array */ - public function deleteNotification($id) + public function deleteNotification(RemoveNotifCampaign $request, Notification $notification) { - $notification = Notification::find($id); if ($notification->delete()) { return redirect()->back(); } else { @@ -178,21 +188,107 @@ class CampagneController extends Controller } } - /** - * @param \App\Coupon $coupon - * - * @return array - */ - // public function deleteCoupon($id) - // { - // $coupon = Coupon::find($id); - // if ($notification->delete()) { - // return redirect()->back(); - // } else { - // session()->flash('delete_nok', 'Erreur lors de la suppression'); - // return redirect()->back(); - // } - // } + // ---- Montre les campagnes avec un compte de démo + public function showDemo(ShowDemoCampaign $request, Campagne $campaign) + { + $siteId = $campaign->camp_siteId; + $demoContact = Contact::where(['contact_siteId' => $siteId, 'contact_ref' => 'DEMO'])->first(); + + if (!isset($demoContact)) { + $req = new \Illuminate\Http\Request(); + $req->setMethod('POST'); + $req->request->add([ + 'refsite' => $siteId, + 'refadherent' => 'DEMO', + 'prenom' => 'Compte', + 'nom' => 'Démonstration', + 'email' => 'demo@demo.fr', + 'code_postal' => '72000', + 'commune' => 'Le Mans', + 'pays' => 250 + ]); + + $contact = new \App\Http\Controllers\ContactController(); + $res = $contact->updateContact($req); + $demoContact = Contact::where(['contact_siteId' => $siteId, 'contact_ref' => 'DEMO'])->first(); + } + + if ($campaign->camp_type->equals(CampaignTypes::mur()) || $campaign->camp_type->equals(CampaignTypes::murss())) { + // ---- DEMONSTRATION POUR LES MURS + $myRequest = new \Illuminate\Http\Request(); + $myRequest->setMethod('POST'); + $myRequest->request->add(['refcamp' => $campaign->camp_id, 'refadherent' => 'DEMO', 'admin' => 1]); + + // ---- EFFACER LES CONTROLES SI EXISTANT + $mur = new \App\Http\Controllers\MurController(); + $res = $mur->deleteControlContact($myRequest); + + // ---- RETOURNE l'URL + $url = "/?refsite=" . $siteId . "&refcamp=" . $campaign->camp_id . "&hash=" . $demoContact->contact_hash; + return json_encode($url); + } else { + // ---- DEMONSTRATION POUR LES PANIERS + $panier = Panier::where(['panier_campId' => $campaign->camp_id, 'panier_contactId' => $demoContact->contact_id, 'panier_pending' => 1])->first(); + + if ($panier) { + $url = "/?basketid=" . $panier->panier_ref; + } else { + // --- Pas de panier Création d'un nouveau + // --- Récuperation des coupons de la campagne + $coupons = $campaign->coupons()->get(); + + foreach ($coupons as $key => $coupon) { + if (!$this->isAvailableToPrint($coupon, $campaign)) { + $coupons->forget($key); + } + } + + $couponsTotal = count($coupons); + $couponsAdded = 0; + $basketContent = ""; + + foreach ($coupons as $coupon) { + $basketContent .= "(1)" . $coupon->coupon_code; + $couponsAdded++; + if ($couponsAdded != $couponsTotal) { + $basketContent .= ","; + } + } + // --- création de l'objet request + $req = new \Illuminate\Http\Request(); + $req->setMethod('POST'); + $req->request->add([ + 'refsite' => $siteId, + 'refcamp' => $campaign->camp_id, + 'refadherent' => $demoContact->contact_ref, + 'nom' => $demoContact->contact_nom, + 'prenom' => $demoContact->contact_prenom, + 'email' => $demoContact->contact_email, + 'code_postal' => $demoContact->contact_cp, + 'commune' => $demoContact->contact_ville, + 'pays' => $demoContact->contact_country, + 'panier' => $basketContent, + 'admin' => 1 + ]); + $panier = new \App\Http\Controllers\PanierController(); + $res = $panier->updateBasket($req); + + dd($res->original, $req); + $url = "/?basketid=" . $res->original['Panier']->Panier; + } + return json_encode($url); + } + } + + // ---- Dépublie une campagne + public function togglePublish(Request $request, Campagne $campaign, int $state) + { + $campaign->camp_published = $state; + $campaign->save(); + + return json_encode($campaign); + } + public function getDetail(Request $request) { @@ -203,7 +299,7 @@ class CampagneController extends Controller $refcamp = $request->refcamp; $campagne = Campagne::where(['camp_id' => $refcamp])->first(); if (!$campagne) { - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 103, 'Message' => 'La campagne n\'existe pas' @@ -216,7 +312,7 @@ class CampagneController extends Controller $refsite = $request->refsite; $site = Site::where(['site_id' => $refsite])->first(); if (!$site) { - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 102, 'Message' => 'Le site n\'existe pas' @@ -227,7 +323,7 @@ class CampagneController extends Controller } if ($campagne->camp_siteId != $site->site_id) { - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 108, 'Message' => 'Pas de campagne pour ce site' @@ -235,7 +331,7 @@ class CampagneController extends Controller } if ($error) { - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 101, 'Message' => 'Des paramètres de la requête sont mal renseignés ou non présents' @@ -244,16 +340,16 @@ class CampagneController extends Controller $enligne = ($campagne->camp_published == 1) ? "oui" : "non"; switch ($campagne->camp_type) { - case 0: + case CampaignTypes::mur(): $type = "mur"; break; - case 1: + case CampaignTypes::panier(): $type = "panier"; break; - case 2: + case CampaignTypes::murss(): $type = "mur sans sélection"; break; - case 3: + case CampaignTypes::paniers(): $type = "panier avec sélection"; break; } @@ -314,7 +410,7 @@ class CampagneController extends Controller $data['Coupons'][] = $codeTraitement; }//end foreach }//end if - return Response::json([ + return response()->json([ 'Statut' => 'OK', 'Détails Campagne' => $data ]); @@ -328,7 +424,7 @@ class CampagneController extends Controller $refcamp = $request->refcamp; $campagne = Campagne::where(['camp_id' => $refcamp])->first(); if (!$campagne) { - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 103, 'Message' => 'La campagne n\'existe pas' @@ -341,7 +437,7 @@ class CampagneController extends Controller $refsite = $request->refsite; $site = Site::where(['site_id' => $refsite])->first(); if (!$site) { - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 102, 'Message' => 'Le site n\'existe pas' @@ -355,7 +451,7 @@ class CampagneController extends Controller $refadherent = $request->refadherent; $contact = Contact::where(['contact_siteId' => $refsite, 'contact_ref' => $refadherent])->first(); if (!$contact) { - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 104, 'Message' => 'Le contact n\'existe pas' @@ -366,7 +462,7 @@ class CampagneController extends Controller } if ($error) { - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 101, 'Message' => 'Des paramètres de la requête sont mal renseignés ou non présents' @@ -399,6 +495,6 @@ class CampagneController extends Controller $data['Codes traitement'][] = $codeTraitement; } } - return Response::json($data); + return response()->json($data); } } diff --git a/app/Http/Controllers/ChangeMarqueController.php b/app/Http/Controllers/ChangeMarqueController.php deleted file mode 100644 index d6d54e4..0000000 --- a/app/Http/Controllers/ChangeMarqueController.php +++ /dev/null @@ -1,24 +0,0 @@ -input('site_id'); - $currentRoute = $request->input('current_route'); - $site = \App\Site::findOrFail($siteId); - session()->put('site', $site); - - return redirect('/bo/' . $currentRoute); - } -} diff --git a/app/Http/Controllers/ContactController.php b/app/Http/Controllers/ContactController.php index f1410ea..03bc677 100644 --- a/app/Http/Controllers/ContactController.php +++ b/app/Http/Controllers/ContactController.php @@ -5,11 +5,21 @@ namespace App\Http\Controllers; use App\Contact; use App\Site; use App\Campagne; -use Response; +use App\Enums\CampaignTypes; +use App\Http\Requests\Contact\LoadWidgetContact; +use App\Http\Requests\Contact\SearchContact; +use App\Http\Requests\Contact\StoreContact; +use App\Http\Requests\Contact\UpdateContact; +use App\Log; +use App\Panier; +use App\PanierContent; +use App\Traits\CouponTrait; use Illuminate\Http\Request; class ContactController extends Controller { + use CouponTrait; + /** * Display a listing of the resource. * @@ -17,46 +27,34 @@ class ContactController extends Controller */ public function index(Request $request) { - $contacts = Contact::where('contact_siteId', session()->get('site')->site_id)->orderBy('contact_createdate', 'DESC')->take(10)->get(); - - return view('contact.index')->with('contacts', $contacts)->with('search', false); + return view('contact.index')->with('search', false); } - public function search_contact(Request $request) + public function search_contact(SearchContact $request) { - session()->put('active_route', 'contact'); - $page = $request->input('page'); - $name = $request->input('searchName'); - $firstname = $request->input('searchFirstname'); - $email = $request->input('searchEmail'); - $cp = $request->input('searchCp'); - $ville = $request->input('searchVille'); - $country = $request->input('searchCountry'); + session()->put('active_route', 'contact'); - if (isset($name) || isset($firstname) || isset($email) || isset($cp) || isset($ville) || isset($country)) { - $contacts = Contact::where('contact_siteId', session()->get('site')->site_id) - ->searchFirstname($firstname) - ->searchName($name) - ->searchEmail($email) - ->searchCp($cp) - ->searchVille($ville) - ->searchCountry($country) - ->paginate(2, ['*'], 'page', $page); - $searchData = array( - 'name' => $name, - 'firstname' => $firstname, - 'email' => $email, - 'cp' => $cp, - 'ville' => $ville, - 'pays' => $country - ); - return view('contact.index')->with('contacts', $contacts)->with('search', true)->with('searchData', $searchData); - } else { - $contacts = Contact::where('contact_siteId', session()->get('site')->site_id)->orderBy('contact_createdate', 'DESC')->take(10)->get(); + $contacts = Contact::where('contact_siteId', session()->get('site')->site_id) + ->searchFirstname($request->searchFirstname) + ->searchName($request->searchName) + ->searchEmail($request->searchEmail) + ->searchCp($request->searchCp) + ->searchVille($request->searchVille) + ->searchCountry($request->searchCountry) + ->orderBy('contact_createdate', 'DESC') + ->paginate(15, ['*'], 'page', $request->page); - return view('contact.index')->with('contacts', $contacts)->with('search', false); - }//end if + $searchData = array( + 'name' => $request->searchName, + 'firstname' => $request->searchFirstname, + 'email' => $request->searchEmail, + 'cp' => $request->searchCp, + 'ville' => $request->searchVille, + 'pays' => $request->searchCountry + ); + + return view('contact.index')->with('contacts', $contacts)->with('search', true)->with('searchData', $searchData); } /** @@ -72,46 +70,22 @@ class ContactController extends Controller /** * Store a newly created resource in storage. * - * @param \Illuminate\Http\Request $request + * @param StoreContact $request * @return \Illuminate\Http\Response */ - public function store(Request $request) + public function store(StoreContact $request) { - $validatedData = $request->validate([ - 'contact_ref' => 'required|unique:contact', - 'contact_nom' => 'required', - 'contact_prenom' => 'required', - 'contact_email' => 'required', - 'contact_cp' => 'required', - 'contact_ville' => 'required', - 'contact_country' => 'required' - ]); - - $contact = new Contact(); - $contact->contact_nom = $request->input('contact_nom'); - $contact->contact_prenom = $request->input('contact_prenom'); - $contact->contact_ref = $request->input('contact_ref'); - $contact->contact_email = $request->input('contact_email'); - $contact->contact_cp = $request->input('contact_cp'); - $contact->contact_ville = $request->input('contact_ville'); - $contact->contact_country = $request->input('contact_country'); - $contact->contact_hash = strtoupper(md5(uniqid(time(), true))); - - // $site = Site::findOrFail(session()->get('site')->id)->first(); + // TODO : Revoir le passage du site $site = session()->get('site'); + $contact = new Contact(); + $contact->fill($request->validated()); $contact->contact_siteId = $site->site_id; - $contact->save(); - $contacts = Contact::where('contact_siteId', session()->get('site')->site_id)->orderBy('contact_createdate', 'DESC')->take(10)->get(); - $campagnes = Campagne::where(['camp_siteId' => $contact->contact_siteId, 'camp_published' => 1])->get(); - - return view('contact.edit', ['contact' => $contact, 'created' => true, 'contacts' => $contacts, 'campagnes' => $campagnes]); + return redirect()->route('contact.edit', ['contact' => $contact]); } - - /** * Show the form for editing the specified resource. * @@ -125,47 +99,26 @@ class ContactController extends Controller return view('contact.edit', ['contact' => $contact, 'campagnes' => $campagnes]); } - /** * Update the specified resource in storage. * - * @param \Illuminate\Http\Request $request - * @param \App\Contact $contact + * @param UpdateContact $request + * @param \App\Contact $contact * @return \Illuminate\Http\Response */ - public function update(Request $request, Contact $contact) + public function update(UpdateContact $request, Contact $contact) { - $validatedData = $request->validate([ - 'contact_ref' => 'required', - 'contact_nom' => 'required', - 'contact_prenom' => 'required', - 'contact_email' => 'required', - 'contact_cp' => 'required', - 'contact_ville' => 'required', - 'contact_country' => 'required' - ]); - - $campagnes = Campagne::where(['camp_siteId' => $contact->contact_siteId, 'camp_published' => 1])->get(); - if ($contact->update($request->all())) { - session()->flash('update_ok', 'La campagne a bien été modifiée'); + $contact->fill($request->validated()); + if ($contact->save()) { + session()->flash('update_ok', __('La campagne a bien été modifiée')); } else { - session()->flash('update_nok', 'Erreur lors de l\'enregistrement'); + session()->flash('update_nok', "Erreur lors de l'enregistrement"); } - return redirect()->route('contact.edit', ['contact' => $contact]); } public function updateContact(Request $request) { - if (isset($request->admin)) { - $source = 'ADMIN'; - } else { - $source = 'API'; - } - // ---- INIT du log - // $log = new Log(); - // $log->log_action = config('constants.updatecontact'); - // $log->log_source = $source; $error = false; // ---- Contrôle des paramètres if (isset($request->refadherent) && $request->refadherent != "") { @@ -215,11 +168,7 @@ class ContactController extends Controller } if ($error) { - // $log->log_params = json_encode(['refadherent' => $request->refadherent, 'refsite'=>$request->refsite, 'nom' => $request->nom, 'prenom' => $request->prenom, 'email' => $request->email, 'code postal' => $request->code_postal, 'commune' => $request->commune]); - // $log->log_statut = 'NOK'; - // $log->log_message = "Des paramètres de la requête sont mal renseignés ou non présents"; - // $log->save(); - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 101, 'Message' => 'Des paramètres de la requête sont mal renseignés ou non présents' @@ -258,12 +207,7 @@ class ContactController extends Controller $contact->contact_hash = strtoupper(md5(uniqid(time(), true))); } } else { - // $log->log_params = json_encode(['refadherent' => $request->refadherent, 'refsite'=>$request->refsite, 'nom' => $request->nom, 'prenom' => $request->prenom, 'email' => $request->email, 'code postal' => $request->code_postal, 'commune' => $request->commune]); - // $log->log_refsite = $request->refsite; - // $log->log_statut = 'NOK'; - // $log->log_message = "Le site spécifié n'existe pas"; - // $log->save(); - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 102, 'Message' => 'Le site spécifié n\'existe pas' @@ -272,21 +216,114 @@ class ContactController extends Controller }//end if if ($contact->save()) { - return Response::json([ + return response()->json([ 'Statut' => 'OK', 'contact' => $contact ]); } else { - // $log->log_params = json_encode(['refadherent' => $request->refadherent, 'refsite'=>$request->refsite, 'nom' => $request->nom, 'prenom' => $request->prenom, 'email' => $request->email, 'code postal' => $request->code_postal, 'commune' => $request->commune]); - // $log->log_refsite = $request->refsite; - // $log->log_statut = 'NOK'; - // $log->log_message = "Une erreur est survenue"; - // $log->save(); - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 109, 'Message' => 'Une erreur est survenue' ]); } } + + public function loadWidget(LoadWidgetContact $request, Contact $contact) + { + $camp_id = $request->camp_id; + $campagne = Campagne::where(['camp_id' => $camp_id])->first(); + $coupons = $campagne->coupons; + + foreach ($coupons as $key => $coupon) { + if (!$this->isAvailableToPrint($coupon, $campagne)) { + $coupons->forget($key); + } + } + + $allLogs = Log::where(['log_refsite' => $campagne->camp_siteId, 'log_contactId' => $contact->contact_id, 'log_statut' => 'OK'])->orderBy('created_at', 'DESC')->get(); + $logs = array(); + foreach ($allLogs as &$l) { + $params = json_decode($l->log_params); + if ($l->log_action == 3 || $l->log_action == 1) { + $l->log_message = json_decode($l->log_message, true); + } + if (intval($params->refcamp) == $camp_id) { + array_push($logs, $l); + } + } + + // ---- Pour les types murs + if ($campagne->camp_type->equals(CampaignTypes::mur()) || $campagne->camp_type->equals(CampaignTypes::murss())) { + foreach ($coupons as &$coupon) { + $coupon->isAvailable = $this->isAvailable($coupon, $campagne, $contact); + } + unset($coupon); + + // Montant total et Nombre réductions disponible + $montantDispo = 0; + $montantTotal = 0; + $nbCoupon = 0; + foreach ($coupons as $coupon) { + $montantTotal += $coupon->coupon_value; + if ($coupon->isAvailable == true) { + $montantDispo += $coupon->coupon_value; + $nbCoupon++; + } + } + + if (count($coupons) === $nbCoupon) { + $reset = false; + } else { + $reset = true; + } + + $campagne->url = url('/') . "/?refsite=" . $campagne->camp_siteId . "&refcamp=" . $camp_id . "&hash=" . $contact->contact_hash; + + $returnHTML = view('widgets.mur') + ->with([ + 'campagne' => $campagne, + 'reset' => $reset, + 'montantTotal' => $montantTotal, + 'montantDispo' => $montantDispo, + 'logs' => $logs, + 'contact' => $contact]) + ->renderSections()['widget']; + + return json_encode(array('success' => true, 'html' => $returnHTML)); + } else { + // ---- Pour les types paniers + $panier = Panier::where(['panier_campId' => $campagne->camp_id, 'panier_contactId' => $contact->contact_id, 'panier_pending' => 1])->first(); + if ($panier) { + $panier_ref = $panier->panier_ref; + foreach ($coupons as &$coupon) { + $paniercontent = PanierContent::where(['panier_content_couponId' => $coupon->coupon_id, 'panier_content_panierId' => $panier->panier_id])->first(); + if ($paniercontent) { + $coupon->qty = $paniercontent->panier_content_quantity; + } else { + $coupon->qty = 0; + } + } + $campagne->url = url('/') . "/?basketid=" . $panier_ref; + unset($coupon); + } else { + $panier_ref = ""; + foreach ($coupons as &$coupon) { + $coupon->qty = 0; + } + $campagne->url = "Pas d'url disponible (panier non créé)"; + } + + $returnHTML = view('widgets.panier') + ->with([ + 'campagne' => $campagne, + 'contact' => $contact, + 'panier' => $panier_ref, + 'logs' => $logs, + 'coupons' => $coupons + ]) + ->renderSections()['widget']; + return json_encode(array('success' => true, 'html' => $returnHTML)); + } + } } diff --git a/app/Http/Controllers/CouponController.php b/app/Http/Controllers/CouponController.php index 1eb0b1a..29c0377 100644 --- a/app/Http/Controllers/CouponController.php +++ b/app/Http/Controllers/CouponController.php @@ -9,9 +9,10 @@ use App\Site; use App\Campagne; use App\Mouvement; use App\Calendrier; +use App\Http\Requests\Coupons\StoreCoupons; +use App\Http\Requests\Coupons\UpdateCoupons; use App\Traits\GenerateBR; use App\Traits\CouponTrait; -use Validator; class CouponController extends Controller { @@ -25,23 +26,16 @@ class CouponController extends Controller */ public function index() { - $campIds = Campagne::where('camp_siteId', session()->get('site')->site_id) - ->get() - ->pluck('camp_id') - ->flatten() - ->toArray(); - - $coupons = Coupon::where('coupon_siteId', session()->get('site')->id)->get(); - + // TODO : Revoir le passage du site + $site = session()->get('site'); + $coupons = Coupon::where('coupon_siteId', $site->id)->get(); foreach ($coupons as $coupon) { // ---- Calcul de la quantité mise à disposition grace aux fonctions dans CouponTrait.php $coupon->coupon_quantity = $this->getFullQuantity($coupon); // ---- Calcul de la quantité restante toutes campagnes comprises grace aux fonctions dans CouponTrait.php - $coupon->coupon_left = 0; -// $coupon->coupon_quantity - $this->getUsedQuantity($coupon); + $coupon->coupon_left = $coupon->coupon_quantity - $this->getUsedQuantity($coupon); } - - return view('coupon.index', ['coupons' => $coupons, 'camps' => $campIds]); + return view('coupon.index', ['coupons' => $coupons]); } /** @@ -51,51 +45,32 @@ class CouponController extends Controller */ public function create() { + // TODO : Revoir le passage du site + $site = session()->get('site'); + $campagnes = Campagne::where('camp_siteId', session()->get('site')->site_id)->get() ->pluck('camp_name', 'camp_id'); - $site = Site::where(['id' => session()->get('site')->id])->first(); + $site = Site::findOrFail($site->id); return view('coupon.create')->with('campagnes', $campagnes)->with('site', $site); } /** * Store a newly created resource in storage. * - * @param \Illuminate\Http\Request $request + * @param StoreCoupons $request * @return \Illuminate\Http\Response */ - public function store(Request $request) + public function store(StoreCoupons $request) { - $data = $request->all(); - - $v = Validator::make($data, [ - 'coupon_validity_text' => 'required', - 'coupon_legal_mentions' => 'required', - 'coupon_decoration_image' => 'required|image|mimes:jpeg,png', - 'coupon_logo_image' => 'required|image|mimes:jpeg,png', - 'coupon_rcs' => 'required', - 'coupon_site_contact' => 'required', - ]); - - $v->sometimes('coupon_gs1code', 'required|image|mimes:jpeg', function ($input) { - return isset($input->coupon_norme); - }); - - if ($v->fails()) { - return redirect('bo/coupon/' . $coupon->coupon_id . '/edit') - ->withErrors($v) - ->withInput(); - } - - + $site_id = $request->site_id; $datas = $this->saveImg($request); + $coupon = new Coupon(); + $coupon->fill($datas); - $coupon = new Coupon(); - $coupon->coupon_code = $datas['coupon_code']; - $coupon->coupon_name = $datas['coupon_name']; - $coupon->coupon_value = $datas['coupon_value']; + // TODO : A revoir au moment ou on enlevera les images mobiles if (isset($datas['coupon_thumbnail1'])) { $coupon->coupon_thumbnail1 = $datas['coupon_thumbnail1']; } @@ -111,46 +86,21 @@ class CouponController extends Controller if (isset($datas['coupon_image'])) { $coupon->coupon_image = $datas['coupon_image']; } - if (isset($datas['coupon_norme'])) { - $coupon->coupon_norme = $datas['coupon_norme']; - $coupon->coupon_gs1code = $datas['coupon_gs1code']; - } - if (isset($datas['coupon_enablelimitquantity'])) { - $coupon->coupon_enablelimitquantity = $datas['coupon_enablelimitquantity']; - $coupon->coupon_limitquantity = $datas['coupon_limitquantity']; - } - if (isset($datas['coupon_enablelimitperiod'])) { - $coupon->coupon_enablelimitperiod = $datas['coupon_enablelimitperiod']; - $coupon->coupon_limitperiod = $datas['coupon_limitperiod']; - } - if (isset($datas['coupon_published'])) { - $coupon->coupon_published = $datas['coupon_published']; - } else { + + //TODO : Voir si on peut mettre le set a 0 dans la request + if (!isset($datas['coupon_published'])) { $coupon->coupon_published = 0; } - if (isset($datas['coupon_legal_mentions_splitted'])) { - $coupon->coupon_legal_mentions_splitted = $datas['coupon_legal_mentions_splitted']; - } else { + if (!isset($datas['coupon_legal_mentions_splitted'])) { $coupon->coupon_legal_mentions_splitted = 0; } - $coupon->coupon_validity = $datas['coupon_validity']; - $coupon->coupon_mindatediffusion = $datas['coupon_mindatediffusion']; - $coupon->coupon_maxdatediffusion = $datas['coupon_maxdatediffusion']; - $coupon->coupon_maxdatevalidity = $datas['coupon_maxdatevalidity']; - $coupon->coupon_validity_text = $datas['coupon_validity_text']; - $coupon->coupon_legal_mentions = $datas['coupon_legal_mentions']; - if (isset($datas['coupon_decoration_image'])) { - $coupon->coupon_decoration_image = $datas['coupon_decoration_image']; - } - if (isset($datas['coupon_logo_image'])) { - $coupon->coupon_logo_image = $datas['coupon_logo_image']; - } - $coupon->coupon_rcs = $datas['coupon_rcs']; - $coupon->coupon_site_contact = $datas['coupon_site_contact']; - $coupon->coupon_seuil_alert = $datas['coupon_seuil_alert']; + $br_path = $this->generateBase($request, $coupon); + $coupon->coupon_image = $br_path; + $coupon->coupon_siteId = $site_id; + $coupon->save(); - $site = Site::find($request->input('site_id')); + $site = Site::findOrFail($site_id); $site->coupons()->save($coupon); @@ -160,15 +110,6 @@ class CouponController extends Controller $coupon->mouvements()->save($mouvement); } - if (isset($datas['mouvementNbre'])) { - foreach ($datas['mouvementNbre'] as $key => $value) { - $mouvement = new Mouvement(); - $mouvement->mouvement_nombre = $value; - $mouvement->mouvement_created_at = $datas['mouvementDate'][$key]; - $coupon->mouvements()->save($mouvement); - } - } - if (isset($datas['startdate'])) { foreach ($datas['startdate'] as $key => $value) { $calendar = new Calendrier(); @@ -178,22 +119,7 @@ class CouponController extends Controller } } - $br_path = $this->generateBase($request, $coupon); - $coupon->coupon_image = $br_path; - $coupon->save(); - - return view('coupon.edit', ['coupon' => $coupon]); - } - - /** - * Display the specified resource. - * - * @param \App\Coupon $coupon - * @return \Illuminate\Http\Response - */ - public function show(Coupon $coupon) - { - return view('coupon.show', ['coupon' => $coupon]); + return redirect()->route('coupon.edit', ['coupon' => $coupon]); } /** @@ -210,33 +136,13 @@ class CouponController extends Controller /** * Update the specified resource in storage. * - * @param \Illuminate\Http\Request $request + * @param UpdateCoupons $request * @param \App\Coupon $coupon * @return \Illuminate\Http\Response */ - public function update(Request $request, Coupon $coupon) + public function update(UpdateCoupons $request, Coupon $coupon) { - - $data = $request->all(); - - $v = Validator::make($data, [ - 'coupon_decoration_image' => 'image|mimes:jpeg,png', - 'coupon_logo_image' => 'image|mimes:jpeg,png', - ]); - - $v->sometimes('coupon_gs1code', 'required|image|mimes:jpeg', function ($input) { - if (isset($input->coupon_gs1code_image_hidden)) { - return false; - } - return isset($input->coupon_norme); - }); - - if ($v->fails()) { - return redirect('bo/coupon/' . $coupon->coupon_id . '/edit') - ->withErrors($v) - ->withInput(); - } - + //TODO : Voir si on peut mettre le set a 0 dans la request if (!isset($request->coupon_enablelimitquantity)) { $coupon->coupon_enablelimitquantity = 0; } @@ -252,11 +158,17 @@ class CouponController extends Controller if (!isset($request->coupon_legal_mentions_splitted)) { $coupon->coupon_legal_mentions_splitted = 0; } - if ($coupon->update($this->saveImg($request))) { - $datas = $this->saveImg($request); + + $datas = $this->saveImg($request); + // On sauvegarde les informations + if ($coupon->update($datas)) { + // Si l'update est OK on met à jour l'image du BR $br_path = $this->generateBase($request, $coupon); $coupon->coupon_image = $br_path; - $coupon->update($this->saveImg($request)); + $coupon->save(); + + // Si on ajoute des BR + // TODO : Gestion de l'erreur si on ajoute une ligne vide if (isset($datas['mouvementNbre'])) { foreach ($datas['mouvementNbre'] as $key => $value) { $mouvement = new Mouvement(); @@ -265,6 +177,8 @@ class CouponController extends Controller $coupon->mouvements()->save($mouvement); } } + // Si on ajoute une plage de diffusion + // TODO : Gestion de l'erreur si on ajoute une ligne vide if (isset($datas['startdate'])) { $coupon->calendriers()->delete(); foreach ($datas['startdate'] as $key => $value) { @@ -295,10 +209,10 @@ class CouponController extends Controller $coupon->coupon_published = 0; $coupon->save(); if ($coupon->delete()) { - return redirect(route('coupon.index')); + return redirect()->route('coupon.index'); } else { session()->flash('delete_nok', 'Erreur lors de la suppression'); - return view('coupon.edit')->with('coupon', $coupon); + return back(); } } @@ -311,8 +225,6 @@ class CouponController extends Controller { $datas = collect($request->all()); - $imgs = collect(array()); - foreach ($datas as $key => $item) { if ($key == 'coupon_image' || $key == 'coupon_gs1code' || $key == 'coupon_decoration_image' || $key == 'coupon_logo_image' || (substr($key, 7, 9) == 'thumbnail' && strlen($key) < 18)) { if (isset($item)) { diff --git a/app/Http/Controllers/CustomersController.php b/app/Http/Controllers/CustomersController.php new file mode 100644 index 0000000..2811c80 --- /dev/null +++ b/app/Http/Controllers/CustomersController.php @@ -0,0 +1,33 @@ +email; + + $site->load("campagnes"); + + if ($email) { + $customer = Customers::where('email', $email)->first(); + if ($customer) { + $publishedCampaign = $site->campagnes->where('camp_published', 1)->pluck('camp_id'); + + $campaigns = Campagne::where('camp_published', 1)->whereHas('printedWalls', function ($q) use ($customer, $publishedCampaign) { + $q->where('customer_id', $customer->id)->whereIn('camp_id', $publishedCampaign); + })->get(); + } + } + + return view('customers.index', compact('site', 'customer', 'campaigns', 'email')); + } +} diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php index 9d15242..e2029da 100644 --- a/app/Http/Controllers/ExportController.php +++ b/app/Http/Controllers/ExportController.php @@ -9,6 +9,7 @@ use App\Traits\CouponTrait; use App\Traits\GraphTrait; use App\Site; use App\Coupon; +use App\Http\Requests\Export\PDFExport; use Datetime; use Illuminate\Http\Request; @@ -34,11 +35,12 @@ class ExportController extends Controller } // ------ AJAX Export - public function export_coupons(Request $request) + public function exportCoupons(PDFExport $request) { - $ids = $request->input('ids'); - $startdate = date('Y-m-d', strtotime($request->input('startdate'))); - $enddate = date('Y-m-d', strtotime($request->input('enddate'))); + $ids = $request->ids; + $startdate = $request->startdate; + $enddate = $request->enddate; + $coupons = Coupon::find($ids); $labels = $this->getLabelsForStats($startdate, $enddate); @@ -47,8 +49,7 @@ class ExportController extends Controller foreach ($coupons as $coupon) { $cpName = str_replace(' ', '', $coupon->coupon_name); - $now = strtotime(now()); - $full_path = 'export/' . $cpName . '_' . $now . '.pdf'; + $full_path = 'export/' . $cpName . '_' . date('d_m_Y') . '.pdf'; $dataCoupon = $this->getDataForCouponAllCamp($labels, $coupon); $coupon->couponQuantity = $this->getFullQuantity($coupon); $coupon->couponFullPrinted = $this->getUsedQuantity($coupon); @@ -71,12 +72,10 @@ class ExportController extends Controller $myPicture->drawScale(array("DrawSubTicks" => true, 'LabelSkip' => 1, 'LabelRotation' => 25, "Mode" => SCALE_MODE_START0, "CycleBackground" => true,"GridR" => 102,"GridG" => 102,"GridB" => 102)); $myPicture->setShadow(true, array("X" => 1,"Y" => 1,"R" => 0,"G" => 0,"B" => 0,"Alpha" => 10)); $myPicture->drawLineChart(array("DisplayColor" => DISPLAY_AUTO)); - $LabelSettings = array("NoTitle" => true); $myPicture->Render('export/temp.png'); $view = view('export.template')->with(['src' => 'export/temp.png', 'coupon' => $coupon, 'startdate' => $startdate, 'enddate' => $enddate])->render(); - // var_dump($view); $pdf->loadHTML($view); $pdf->save($full_path); array_push($paths, $full_path); diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php deleted file mode 100644 index c4b2fb8..0000000 --- a/app/Http/Controllers/HomeController.php +++ /dev/null @@ -1,35 +0,0 @@ -middleware('auth'); - } - - /** - * Show the application dashboard. - * - * @return \Illuminate\Http\Response - */ - public function index() - { - $test = Campagne::find(102); - - Debugbar::info($test); - - return view('home')->with([ - 'test' => $test, - ]); - } -} diff --git a/app/Http/Controllers/MaintenanceController.php b/app/Http/Controllers/MaintenanceController.php index 4b45cfc..6a76194 100644 --- a/app/Http/Controllers/MaintenanceController.php +++ b/app/Http/Controllers/MaintenanceController.php @@ -2,6 +2,8 @@ namespace App\Http\Controllers; +use App\Http\Requests\Users\StoreUsers; +use App\Http\Requests\Users\UpdateUsers; use App\Site; use App\User; use App\Log; @@ -15,15 +17,8 @@ class MaintenanceController extends Controller { $users = User::all(); - foreach ($users as &$user) { - $sites = $user->sites()->get(); - $user->linkedSites = ""; - foreach ($sites as $site) { - $user->linkedSites .= '

' . $site->site_nom . '

'; - } - } $dates = array(date('m/Y')); - for ($i = 1; $i < 12; $i++) { + for ($i = 1; $i < 6; $i++) { $datebefore = date("m/Y", strtotime("-" . $i . " months")); array_push($dates, $datebefore); } @@ -33,14 +28,6 @@ class MaintenanceController extends Controller array_push($count, [$month, $logs]); } - // $logArray = array(); - // foreach ($logs as $logMonth) { - // $count = count($logMonth); - // foreach ($logMonth as $log) { - // $date = Carbon::parse($log->created_at)->format('m/Y'); - // } - // array_push($logArray, [$date, $count]); - // } return view('maintenance.index')->with(['users' => $users, 'logs' => $count]); } @@ -50,38 +37,17 @@ class MaintenanceController extends Controller return view('user.create')->with(['sites' => $sites]); } - public function userstore(Request $request) + public function userstore(StoreUsers $request) { - - $validatedData = $request->validate([ - 'email' => 'required|unique:users|email', - 'name' => 'required', - 'password' => 'required' - ]); - - $nom = $request->name; - $email = $request->email; - $password = $request->password; - $role = $request->roles; - - $user = new User(); - $user->name = $nom; - $user->email = $email; - $user->password = Hash::make($password); - $user->roles = $role; + $user = new User(); + $user->fill($request->validated()); + $user->password = Hash::make($request->password); $user->save(); - // MAJ des sites liés - if (isset($request->site)) { - $siteids = $request->site; - $sites = Site::whereIn('site_id', $siteids)->get()->pluck('id')->toArray(); - $user->sites()->sync($sites); - } + $sites = Site::whereIn('site_id', $request->site)->get()->pluck('id')->toArray(); + $user->sites()->sync($sites); - - $sites = Site::all(); - $usersites = $user->sites()->get()->pluck('site_id')->toArray(); - return view('user.edit')->with(['user' => $user, 'sites' => $sites, 'usersites' => $usersites]); + return redirect()->route('maintenance.user.edit', ['user' => $user]); } public function useredit(User $user) @@ -91,55 +57,32 @@ class MaintenanceController extends Controller return view('user.edit')->with(['user' => $user, 'sites' => $sites, 'usersites' => $usersites]); } - public function userdestroy(User $user) + public function userupdate(UpdateUsers $request, User $user) { - if ($user->delete()) { - return redirect(route('maintenance.show')); - } else { - session()->flash('delete_nok', 'Erreur lors de la suppression'); - return view('maintenance.user.edit')->with('user', $user); - } - } - - public function userupdate(Request $request, User $user) - { - // MAJ du User - $user->name = $request->name; - $user->email = $request->email; - $user->roles = $request->roles; + //MAJ du User + $user->fill($request->validated()); $user->save(); - // MAJ des sites liés - $siteids = $request->site; - $sites = Site::whereIn('site_id', $siteids)->get()->pluck('id')->toArray(); + //MAJ des sites liés + $sites = Site::whereIn('site_id', $request->site)->get()->pluck('id')->toArray(); $user->sites()->sync($sites); - $marques = Auth::user()->sites; + $marques = Auth::user()->sites; $submarques = $marques->map(function ($marque) { return $marque->only(['id', 'site_nom', 'site_id', 'site_logo']); }); session()->put('marques', $submarques); + return redirect()->route('maintenance.show'); + } - $users = User::all(); - - foreach ($users as &$user) { - $sites = $user->sites()->get(); - $user->linkedSites = ""; - foreach ($sites as $site) { - $user->linkedSites .= '

' . $site->site_nom . '

'; - } + public function userdestroy(User $user) + { + if ($user->delete()) { + return redirect()->route('maintenance.show'); + } else { + session()->flash('delete_nok', 'Erreur lors de la suppression'); + return view('maintenance.user.edit')->with('user', $user); } - $dates = array('02/2019'); - for ($i = 1; $i < 12; $i++) { - $datebefore = date("m/Y", strtotime("-" . $i . " months")); - array_push($dates, $datebefore); - } - $count = array(); - foreach ($dates as $month) { - $logs = Log::where(['log_statut' => 'NOK'])->whereMonth('created_at', $month)->count(); - array_push($count, [$month, $logs]); - } - return view('maintenance.index')->with(['users' => $users, 'logs' => $count]); } } diff --git a/app/Http/Controllers/MouvementController.php b/app/Http/Controllers/MouvementController.php deleted file mode 100644 index 427d5a0..0000000 --- a/app/Http/Controllers/MouvementController.php +++ /dev/null @@ -1,28 +0,0 @@ -mouvement_type = $request->input('mouvement_type'); - $mouvement->mouvement_nombre = $request->input('mouvement_nombre'); - $mouvement->mouvement_commentaire = $request->input('mouvement_commentaire'); - - $coupon = Coupon::find($request->input('coupon_id')); - - $coupon->mouvements()->save($mouvement); - } -} diff --git a/app/Http/Controllers/MurController.php b/app/Http/Controllers/MurController.php index 94bb192..1c6283a 100644 --- a/app/Http/Controllers/MurController.php +++ b/app/Http/Controllers/MurController.php @@ -9,7 +9,6 @@ use App\Mur; use App\Log; use App\Traits\CouponTrait; use App\Traits\ThanksTrait; -use Response; class MurController extends Controller { @@ -28,7 +27,7 @@ class MurController extends Controller if (!isset($datas['refsite'])) { abort(404); } - if (!isset($campagne) || $campagne->camp_published == 0 || ($campagne->camp_type != 0 && $campagne->camp_type != 2)) { + if (!isset($campagne) || $campagne->camp_published == 0 || ($campagne->camp_type->value != 0 && $campagne->camp_type->value != 2)) { abort(404); } @@ -123,7 +122,7 @@ class MurController extends Controller $log->log_message = "Des paramètres de la requête sont mal renseignés ou non présents"; $log->save(); - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 101, 'Message' => 'Des paramètres de la requête sont mal renseignés ou non présents' @@ -138,7 +137,7 @@ class MurController extends Controller $log->log_statut = 'NOK'; $log->log_message = "La campagne n'existe pas"; $log->save(); - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 103, 'Message' => 'La campagne n\'existe pas' @@ -156,7 +155,7 @@ class MurController extends Controller $log->log_message = "Le contact n'existe pas"; $log->save(); - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 104, 'Message' => 'Le contact n\'existe pas' @@ -173,7 +172,7 @@ class MurController extends Controller $log->log_statut = 'OK'; $log->log_message = $mur . " enregistrements ont été mis à jour."; $log->save(); - return Response::json([ + return response()->json([ 'Statut' => 'OK', 'Message' => $mur . " enregistrements ont été mis à jour." ]); @@ -184,7 +183,7 @@ class MurController extends Controller $log->log_statut = 'OK'; $log->log_message = 'Aucun contrôle à effacer'; $log->save(); - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 110, 'Message' => 'Aucun contrôle à effacer' @@ -201,7 +200,7 @@ class MurController extends Controller if (isset($request->refcamp) && $request->refcamp != "") { $refcamp = $request->refcamp; } else { - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 101, 'Message' => 'Des paramètres de la requête sont mal renseignés ou non présents' @@ -211,7 +210,7 @@ class MurController extends Controller // ------ Recuperation de la campagne $camp = Campagne::where(['camp_id' => $refcamp])->first(); if (!$camp) { - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 103, 'Message' => 'La campagne n\'existe pas' @@ -221,7 +220,7 @@ class MurController extends Controller // ------ Récupération des coupons de la campagne $coupons = $camp->coupons()->where(['coupon_published' => 1])->get(); if (count($coupons) == 0) { - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 105, 'Message' => 'La campagne est vide' @@ -237,7 +236,7 @@ class MurController extends Controller $montantTotal = number_format($montantTotal, 2, ",", " "); $montantTotal = str_pad($montantTotal, 6, "0", STR_PAD_LEFT); - return Response::json([ + return response()->json([ 'Statut' => 'OK', 'Message' => $montantTotal ]); @@ -265,7 +264,7 @@ class MurController extends Controller } if ($error) { - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 101, 'Message' => 'Des paramètres de la requête sont mal renseignés ou non présents' @@ -275,7 +274,7 @@ class MurController extends Controller $campagne = Campagne::where(['camp_id' => $refcamp])->where('camp_type', 'IN', array(0,2))->first(); if (!$campagne) { - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 103, 'Message' => 'La campagne n\'existe pas' @@ -284,7 +283,7 @@ class MurController extends Controller $contact = Contact::where(['contact_siteId' => $refsite, 'contact_ref' => $refadherent])->first(); if (!$contact) { - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 104, 'Message' => 'Le contact n\'existe pas' @@ -308,7 +307,7 @@ class MurController extends Controller $montantDispo = number_format($montantDispo, 2, ",", " "); $montantDispo = str_pad($montantDispo, 6, "0", STR_PAD_LEFT); - return Response::json([ + return response()->json([ 'Statut' => 'OK', 'Message' => $montantDispo ]); diff --git a/app/Http/Controllers/PanierController.php b/app/Http/Controllers/PanierController.php index 5bbed0a..de7eead 100644 --- a/app/Http/Controllers/PanierController.php +++ b/app/Http/Controllers/PanierController.php @@ -7,12 +7,12 @@ use App\Panier; use App\Campagne; use App\Coupon; use App\Contact; +use App\Enums\CampaignTypes; use App\Site; use App\Log; use App\PanierContent; use App\Traits\CouponTrait; use App\Traits\ThanksTrait; -use Response; class PanierController extends Controller { @@ -40,7 +40,7 @@ class PanierController extends Controller } session()->put('campagne', $campagne); - if ($campagne->camp_type == 1) { + if ($campagne->camp_type->equals(CampaignTypes::panier())) { $coupons = $this->getCouponsPublished($campagne); } else { $panierContents = $this->getPanierContentPublished($panier); @@ -97,7 +97,7 @@ class PanierController extends Controller $log->log_message = "Le panier n'existe pas"; $log->save(); - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 106, 'Message' => 'Le panier n\'existe pas' @@ -115,7 +115,7 @@ class PanierController extends Controller $log->log_statut = 'NOK'; $log->log_message = "Des paramètres de la requête sont mal renseignés ou non présents"; $log->save(); - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 101, 'Message' => 'Des paramètres de la requête sont mal renseignés ou non présents' @@ -136,7 +136,7 @@ class PanierController extends Controller $log->log_statut = 'NOK'; $log->log_message = "Des paramètres de la requête sont mal renseignés ou non présents"; $log->save(); - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 101, 'Message' => 'Des paramètres de la requête sont mal renseignés ou non présents' @@ -148,7 +148,7 @@ class PanierController extends Controller foreach ($coupons as $code => $qty) { array_push($panierCoupons, ['cp_code' => $code, 'qty' => $qty]); } - if (sizeof($coupons) == 0) { + if (count($coupons) == 0) { die(); } } else { @@ -156,7 +156,7 @@ class PanierController extends Controller $log->log_statut = 'NOK'; $log->log_message = "Des paramètres de la requête sont mal renseignés ou non présents"; $log->save(); - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 101, 'Message' => 'Des paramètres de la requête sont mal renseignés ou non présents' @@ -196,7 +196,7 @@ class PanierController extends Controller $log->log_message = "Le coupon ne correspond pas aux coupons liés a la campagne"; $log->save(); - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 110, 'Message' => 'Le coupon ne correspond pas aux coupons liés a la campagne' @@ -254,12 +254,12 @@ class PanierController extends Controller $response->panierContent = $panCont; - return Response::json([ + return response()->json([ 'Statut' => 'OK', 'Panier' => $response ]); } else { - return Response::json([ + return response()->json([ 'Statut' => 'OK', 'Panier' => $panier ]); @@ -269,7 +269,7 @@ class PanierController extends Controller $log->log_statut = 'NOK'; $log->log_message = "Le panier n'existe pas"; $log->save(); - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 106, 'Message' => 'Le panier n\'existe pas' @@ -280,7 +280,7 @@ class PanierController extends Controller $log->log_statut = 'NOK'; $log->log_message = "Le panier n'existe pas"; $log->save(); - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 106, 'Message' => 'Le panier n\'existe pas' @@ -292,13 +292,13 @@ class PanierController extends Controller if (isset($request->refcamp) && $request->refcamp != "") { $refcamp = $request->refcamp; $campagne = Campagne::where(['camp_id' => $refcamp])->first(); - if (!$campagne || $campagne->camp_published == 0 || ($campagne->camp_type != 1 && $campagne->camp_type != 3)) { + if (!$campagne || $campagne->camp_published == 0 || ($campagne->camp_type->value != 1 && $campagne->camp_type->value != 3)) { $log->log_params = json_encode(['refcamp' => $refcamp]); $log->log_statut = 'NOK'; $log->log_message = "La campagne n'existe pas ou n'est pas un panier"; $log->save(); - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 103, 'Message' => 'La campagne n\'existe pas ou n\'est pas un panier' @@ -320,7 +320,7 @@ class PanierController extends Controller if (isset($request->refsite) && $request->refsite != "") { $refsite = $request->refsite; if ($refsite != $campagne->camp_siteId) { - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 103, 'Message' => 'La campagne ne correspond à aucune campagnes du site' @@ -381,7 +381,7 @@ class PanierController extends Controller } } } - if (sizeof($coupons) == 0) { + if (count($coupons) == 0) { die(); } } else { @@ -403,7 +403,7 @@ class PanierController extends Controller $log->log_message = "Des paramètres de la requête sont mal renseignés ou non présents"; $log->save(); - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 101, 'Message' => 'Des paramètres de la requête sont mal renseignés ou non présents' @@ -442,7 +442,7 @@ class PanierController extends Controller $log->log_message = "Le site spécifié n'existe pas"; $log->save(); - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 102, 'Message' => 'Le site spécifié n\'existe pas' @@ -454,7 +454,7 @@ class PanierController extends Controller $log->log_statut = 'NOK'; $log->log_message = "Une erreur est survenue"; $log->save(); - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 109, 'Message' => 'Une erreur est survenue' @@ -551,12 +551,12 @@ class PanierController extends Controller $response->panierContent = $panCont; - return Response::json([ + return response()->json([ 'Statut' => 'OK', 'Panier' => $response ]); } else { - return Response::json([ + return response()->json([ 'Statut' => 'OK', 'Panier' => $panier ]); @@ -570,7 +570,7 @@ class PanierController extends Controller if (isset($request->basketid) && $request->basketid != "") { $basketid = $request->basketid; } else { - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 106, 'Message' => 'Le panier n\'existe pas' @@ -579,7 +579,7 @@ class PanierController extends Controller $panier = Panier::where('panier_ref', $basketid)->first(); if (!$panier) { - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 106, 'Message' => 'Le panier n\'existe pas' @@ -594,7 +594,7 @@ class PanierController extends Controller $contact = $panier->contact()->where(['contact_id' => $panier->panier_contactId])->first(); if (!$contact) { - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 104, 'Message' => 'Le contact n\'existe pas' @@ -603,7 +603,7 @@ class PanierController extends Controller $campagne = $panier->campagne()->where(['camp_id' => $panier->panier_campId])->first(); if (!$campagne) { - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 103, 'Message' => 'La campagne n\'existe pas' @@ -612,7 +612,7 @@ class PanierController extends Controller $site = $campagne->site()->first(); if ($site === null) { - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 102, 'Message' => 'Le site n\'existe pas' @@ -621,7 +621,7 @@ class PanierController extends Controller $allPanierContent = $panier->panierContents()->get(); if ($allPanierContent->count() == 0) { - return Response::json([ + return response()->json([ 'Statut' => 'KO', 'Code' => 108, 'Message' => 'Le panier n\'a pas de coupons liés' @@ -649,7 +649,7 @@ class PanierController extends Controller $response->panierContent = $panCont; - return Response::json([ + return response()->json([ 'Statut' => 'OK', 'Informations Panier' => $response ]); diff --git a/app/Http/Controllers/StatistiqueController.php b/app/Http/Controllers/StatistiqueController.php index 3df7c33..a2e9f59 100644 --- a/app/Http/Controllers/StatistiqueController.php +++ b/app/Http/Controllers/StatistiqueController.php @@ -25,20 +25,6 @@ class StatistiqueController extends Controller if (isset($site->campagnes)) { $campagnes = $site->campagnes; foreach ($campagnes as $campagne) { - switch ($campagne->camp_type) { - case 0: - $campagne->camp_type = "Mur"; - break; - case 1: - $campagne->camp_type = "Panier"; - break; - case 2: - $campagne->camp_type = "Mur sans sélection"; - break; - case 3: - $campagne->camp_type = "Panier Sélectionnable"; - break; - } $coupons = $campagne->coupons; foreach ($coupons as $coupon) { $coupon->quantity = $this->getFullQuantity($coupon); @@ -62,20 +48,6 @@ class StatistiqueController extends Controller { $count = 0; $colors = config('constants.colors'); - switch ($campagne->camp_type) { - case 0: - $campagne->camp_type = "Mur"; - break; - case 1: - $campagne->camp_type = "Panier"; - break; - case 2: - $campagne->camp_type = "Mur sans sélection"; - break; - case 3: - $campagne->camp_type = "Panier Sélectionnable"; - break; - } $coupons = $campagne->coupons; foreach ($coupons as $coupon) { @@ -92,7 +64,7 @@ class StatistiqueController extends Controller return view('stats.detail')->with(['campagne' => $campagne, 'startdate' => $startdate, 'enddate' => $enddate]); } - public function load_stats(Request $request) + public function loadStats(Request $request) { $campid = $request->campid; $startdate = $request->startdate; diff --git a/app/Http/Requests/BrandChange/InvokeBrandChange.php b/app/Http/Requests/BrandChange/InvokeBrandChange.php new file mode 100644 index 0000000..29dbc2f --- /dev/null +++ b/app/Http/Requests/BrandChange/InvokeBrandChange.php @@ -0,0 +1,32 @@ +roles === UserRoles::superadmin()->value || Auth::user()->roles === UserRoles::admin()->value); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'site_id' => 'exists:site,id|required' + ]; + } +} diff --git a/app/Http/Requests/Campaign/AddCouponsCampaign.php b/app/Http/Requests/Campaign/AddCouponsCampaign.php new file mode 100644 index 0000000..70cb340 --- /dev/null +++ b/app/Http/Requests/Campaign/AddCouponsCampaign.php @@ -0,0 +1,32 @@ +roles === UserRoles::superadmin()->value || Auth::user()->roles === UserRoles::admin()->value); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'ids' => 'required' + ]; + } +} diff --git a/app/Http/Requests/Campaign/DeleteCampaign.php b/app/Http/Requests/Campaign/DeleteCampaign.php new file mode 100644 index 0000000..ea20b3f --- /dev/null +++ b/app/Http/Requests/Campaign/DeleteCampaign.php @@ -0,0 +1,32 @@ +roles === UserRoles::superadmin()->value || Auth::user()->roles === UserRoles::admin()->value); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/Campaign/RemoveCouponCampaign.php b/app/Http/Requests/Campaign/RemoveCouponCampaign.php new file mode 100644 index 0000000..03fea9c --- /dev/null +++ b/app/Http/Requests/Campaign/RemoveCouponCampaign.php @@ -0,0 +1,32 @@ +roles === UserRoles::superadmin()->value || Auth::user()->roles === UserRoles::admin()->value); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/Campaign/RemoveNotifCampaign.php b/app/Http/Requests/Campaign/RemoveNotifCampaign.php new file mode 100644 index 0000000..58940b0 --- /dev/null +++ b/app/Http/Requests/Campaign/RemoveNotifCampaign.php @@ -0,0 +1,32 @@ +roles === UserRoles::superadmin()->value || Auth::user()->roles === UserRoles::admin()->value); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/Campaign/ShowDemoCampaign.php b/app/Http/Requests/Campaign/ShowDemoCampaign.php new file mode 100644 index 0000000..d937afd --- /dev/null +++ b/app/Http/Requests/Campaign/ShowDemoCampaign.php @@ -0,0 +1,32 @@ +roles === UserRoles::superadmin()->value || Auth::user()->roles === UserRoles::admin()->value); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/Campaign/StoreCampaign.php b/app/Http/Requests/Campaign/StoreCampaign.php new file mode 100644 index 0000000..6853f6a --- /dev/null +++ b/app/Http/Requests/Campaign/StoreCampaign.php @@ -0,0 +1,35 @@ +roles === UserRoles::superadmin()->value || Auth::user()->roles === UserRoles::admin()->value); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'camp_name' => 'min:5|required', + 'camp_type' => 'integer|required', + 'camp_published' => 'integer|required', + 'camp_limitedPrinting' => 'sometimes|integer|required' + ]; + } +} diff --git a/app/Http/Requests/Campaign/StoreNotifCampaign.php b/app/Http/Requests/Campaign/StoreNotifCampaign.php new file mode 100644 index 0000000..f997d4d --- /dev/null +++ b/app/Http/Requests/Campaign/StoreNotifCampaign.php @@ -0,0 +1,39 @@ +roles === UserRoles::superadmin()->value || Auth::user()->roles === UserRoles::admin()->value); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'notif_name' => 'min:5|required', + 'notif_email' => [ + 'required', + Rule::unique('notification')->where(function ($query) { + return $query->where('notif_campId', $this->campaign->camp_id); + }), + ] + ]; + } +} diff --git a/app/Http/Requests/Campaign/UpdateCampaign.php b/app/Http/Requests/Campaign/UpdateCampaign.php new file mode 100644 index 0000000..0dea541 --- /dev/null +++ b/app/Http/Requests/Campaign/UpdateCampaign.php @@ -0,0 +1,35 @@ +roles === UserRoles::superadmin()->value || Auth::user()->roles === UserRoles::admin()->value); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'camp_name' => 'min:5|required', + 'camp_type' => 'integer|required', + 'camp_published' => 'integer|required', + 'camp_limitedPrinting' => 'sometimes|integer|required' + ]; + } +} diff --git a/app/Http/Requests/Contact/LoadWidgetContact.php b/app/Http/Requests/Contact/LoadWidgetContact.php new file mode 100644 index 0000000..78dfea1 --- /dev/null +++ b/app/Http/Requests/Contact/LoadWidgetContact.php @@ -0,0 +1,32 @@ +roles === UserRoles::superadmin()->value || Auth::user()->roles === UserRoles::admin()->value); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'camp_id' => 'required|exists:campagne,camp_id' + ]; + } +} diff --git a/app/Http/Requests/Contact/SearchContact.php b/app/Http/Requests/Contact/SearchContact.php new file mode 100644 index 0000000..33f0c14 --- /dev/null +++ b/app/Http/Requests/Contact/SearchContact.php @@ -0,0 +1,38 @@ +roles === UserRoles::superadmin()->value || Auth::user()->roles === UserRoles::admin()->value); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'page' => 'sometimes|integer|nullable', + 'searchName' => 'sometimes|alpha|max:50|nullable', + 'searchFirstname' => 'sometimes|alpha|max:50|nullable', + 'searchEmail' => 'sometimes|email|max:100|nullable', + 'searchCp' => 'sometimes|integer|max:10|nullable', + 'searchVille' => 'sometimes|alpha|max:50|nullable', + 'searchCountry' => 'sometimes|integer|nullable', + ]; + } +} diff --git a/app/Http/Requests/Contact/StoreContact.php b/app/Http/Requests/Contact/StoreContact.php new file mode 100644 index 0000000..d13e5ff --- /dev/null +++ b/app/Http/Requests/Contact/StoreContact.php @@ -0,0 +1,38 @@ +roles === UserRoles::superadmin()->value || Auth::user()->roles === UserRoles::admin()->value); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'contact_ref' => 'required|unique:contact', + 'contact_nom' => 'min:3|max:50|required', + 'contact_prenom' => 'min:3|max:50|required', + 'contact_email' => 'email|required', + 'contact_cp' => 'max:10|required', + 'contact_ville' => 'max:50|required', + 'contact_country' => 'integer|required' + ]; + } +} diff --git a/app/Http/Requests/Contact/UpdateContact.php b/app/Http/Requests/Contact/UpdateContact.php new file mode 100644 index 0000000..8665ed5 --- /dev/null +++ b/app/Http/Requests/Contact/UpdateContact.php @@ -0,0 +1,42 @@ +roles === UserRoles::superadmin()->value || Auth::user()->roles === UserRoles::admin()->value); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'contact_ref' => [ + 'required', + Rule::unique('contact')->ignore($this->contact->contact_id, 'contact_id'), + ], + 'contact_nom' => 'min:3|max:50|required', + 'contact_prenom' => 'min:3|max:50|required', + 'contact_email' => 'email|required', + 'contact_cp' => 'max:10|required', + 'contact_ville' => 'max:50|required', + 'contact_country' => 'integer|required' + ]; + } +} diff --git a/app/Http/Requests/Coupons/StoreCoupons.php b/app/Http/Requests/Coupons/StoreCoupons.php new file mode 100644 index 0000000..10ebf7d --- /dev/null +++ b/app/Http/Requests/Coupons/StoreCoupons.php @@ -0,0 +1,75 @@ +roles === UserRoles::superadmin()->value || Auth::user()->roles === UserRoles::admin()->value); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'coupon_code' => [ + 'required', + Rule::unique('coupon', 'coupon_code') + ], + 'coupon_name' => 'string|required', + 'coupon_value' => 'numeric|required', + 'coupon_validity_text' => 'required', + 'coupon_legal_mentions' => 'required', + 'coupon_decoration_image' => 'required|image|mimes:jpeg,png', + 'coupon_logo_image' => 'required|image|mimes:jpeg,png', + 'coupon_rcs' => 'required', + 'coupon_site_contact' => 'required', + 'coupon_seuil_alert' => 'integer|required', + + 'coupon_enablelimitperiod' => 'sometimes|integer|required', + 'coupon_enablelimitquantity' => 'sometimes|integer|required', + 'coupon_mindatediffusion' => 'nullable', + 'coupon_maxdatediffusion' => 'nullable', + 'coupon_validity' => 'integer|required', + 'coupon_maxdatevalidity' => 'nullable', + + 'coupon_legal_mentions_splitted' => 'sometimes|integer|required', + ]; + } + + /** + * Configure the validator instance. + * + * @param \Illuminate\Validation\Validator $validator + * @return void + */ + public function withValidator($validator) + { + $validator->sometimes('coupon_gs1code', 'required|image|mimes:jpeg', function ($input) { + return isset($input->coupon_norme); + }); + + $validator->sometimes('coupon_limitquantity', 'integer|required', function ($input) { + return !is_null($input->coupon_enablelimitquantity); + }); + + $validator->sometimes('coupon_limitperiod', 'integer|required', function ($input) { + return !is_null($input->coupon_enablelimitperiod); + }); + } +} diff --git a/app/Http/Requests/Coupons/UpdateCoupons.php b/app/Http/Requests/Coupons/UpdateCoupons.php new file mode 100644 index 0000000..d0808ee --- /dev/null +++ b/app/Http/Requests/Coupons/UpdateCoupons.php @@ -0,0 +1,82 @@ +roles === UserRoles::superadmin()->value || Auth::user()->roles === UserRoles::admin()->value); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'coupon_code' => [ + 'required', + Rule::unique('coupon', 'coupon_code')->ignore($this->coupon->coupon_id, 'coupon_id') + ], + 'coupon_name' => 'string|required', + 'coupon_value' => 'numeric|required', + 'coupon_seuil_alert' => 'integer|required', + 'coupon_enablelimitperiod' => 'sometimes|integer|required', + 'coupon_enablelimitquantity' => 'sometimes|integer|required', + 'coupon_mindatediffusion' => 'nullable', + 'coupon_maxdatediffusion' => 'nullable', + 'coupon_validity' => 'integer|required', + 'coupon_maxdatevalidity' => 'nullable', + + 'coupon_treatment_center' => 'integer|required', + 'coupon_validity_text' => 'required', + 'coupon_legal_mentions' => 'required', + + 'coupon_rcs' => 'required', + 'coupon_site_contact' => 'email|required', + + 'coupon_norme' => 'sometimes|integer|required', + 'coupon_published' => 'sometimes|integer|required', + 'coupon_legal_mentions_splitted' => 'sometimes|integer|required', + 'coupon_decoration_image' => 'image|mimes:jpeg,png', + 'coupon_logo_image' => 'image|mimes:jpeg,png', + ]; + } + + /** + * Configure the validator instance. + * + * @param \Illuminate\Validation\Validator $validator + * @return void + */ + public function withValidator($validator) + { + $validator->sometimes('coupon_gs1code', 'required|image|mimes:jpeg', function ($input) { + if (isset($input->coupon_gs1code_image_hidden)) { + return false; + } + return isset($input->coupon_norme); + }); + + $validator->sometimes('coupon_limitquantity', 'integer|required', function ($input) { + return !is_null($input->coupon_enablelimitquantity); + }); + + $validator->sometimes('coupon_limitperiod', 'integer|required', function ($input) { + return !is_null($input->coupon_enablelimitperiod); + }); + } +} diff --git a/app/Http/Requests/Customers/BasketRequest.php b/app/Http/Requests/Customers/BasketRequest.php new file mode 100644 index 0000000..706ad6e --- /dev/null +++ b/app/Http/Requests/Customers/BasketRequest.php @@ -0,0 +1,45 @@ + 'string|exists:campagne,camp_id|required', + 'hashcode' => 'string|exists:customers,hashcode|required' + ]; + } + + protected function failedValidation(Validator $validator) + { + if ($this->wantsJson()) { + throw new HttpResponseException(response()->json(new StatusResource([ + 'status' => 'KO', + 'code' => 422, + 'message' => $validator->errors()->messages() + ]), 422)); + } + } +} diff --git a/app/Http/Requests/Customers/PrintBasketRequest.php b/app/Http/Requests/Customers/PrintBasketRequest.php new file mode 100644 index 0000000..d9db769 --- /dev/null +++ b/app/Http/Requests/Customers/PrintBasketRequest.php @@ -0,0 +1,52 @@ + 'string|exists:campagne,camp_id|required', + 'hashcode' => 'string|exists:customers,hashcode|required' + ]; + } + + /** + * failedValidation + * @param Validator $validator + * @return void + * @throws BindingResolutionException + * @throws HttpResponseException + */ + protected function failedValidation(Validator $validator) + { + if ($this->wantsJson()) { + throw new HttpResponseException(response()->json(new StatusResource([ + 'status' => 'KO', + 'code' => 422, + 'message' => $validator->errors()->messages() + ]), 422)); + } + } +} diff --git a/app/Http/Requests/Customers/SearchCustomers.php b/app/Http/Requests/Customers/SearchCustomers.php new file mode 100644 index 0000000..25357d2 --- /dev/null +++ b/app/Http/Requests/Customers/SearchCustomers.php @@ -0,0 +1,33 @@ +roles, [UserRoles::superadmin()->value, UserRoles::admin()->value]); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'email' => 'sometimes|email|required' + ]; + } +} diff --git a/app/Http/Requests/Export/PDFExport.php b/app/Http/Requests/Export/PDFExport.php new file mode 100644 index 0000000..82f938e --- /dev/null +++ b/app/Http/Requests/Export/PDFExport.php @@ -0,0 +1,34 @@ +roles === UserRoles::superadmin()->value || Auth::user()->roles === UserRoles::admin()->value); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'ids' => 'required', + 'startdate' => 'date|required', + 'enddate' => 'date|required' + ]; + } +} diff --git a/app/Http/Requests/Users/StoreUsers.php b/app/Http/Requests/Users/StoreUsers.php new file mode 100644 index 0000000..e0f7cb9 --- /dev/null +++ b/app/Http/Requests/Users/StoreUsers.php @@ -0,0 +1,36 @@ +roles === UserRoles::superadmin()->value || Auth::user()->roles === UserRoles::admin()->value); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'email' => 'required|unique:users|email', + 'name' => 'required', + 'password' => 'min:8|required', + 'site' => 'required', + 'roles' => 'required' + ]; + } +} diff --git a/app/Http/Requests/Users/UpdateUsers.php b/app/Http/Requests/Users/UpdateUsers.php new file mode 100644 index 0000000..eaf7b34 --- /dev/null +++ b/app/Http/Requests/Users/UpdateUsers.php @@ -0,0 +1,39 @@ +roles === UserRoles::superadmin()->value || Auth::user()->roles === UserRoles::admin()->value); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'email' => [ + 'required', + Rule::unique('users')->ignore($this->user->id) + ], + 'name' => 'required', + 'site' => 'required', + 'roles' => 'required' + ]; + } +} diff --git a/app/Http/Resources/CouponsResource.php b/app/Http/Resources/CouponsResource.php index ede0c41..2df0b81 100644 --- a/app/Http/Resources/CouponsResource.php +++ b/app/Http/Resources/CouponsResource.php @@ -14,7 +14,7 @@ class CouponsResource extends JsonResource */ public function toArray($request) { - return [ + $coupon = [ 'code' => $this->coupon_code, 'name' => $this->coupon_name, 'value' => $this->coupon_value, @@ -25,6 +25,13 @@ class CouponsResource extends JsonResource 'mobile_image_unavailable' => $this->returnUrl($this->coupon_thumbnail4), 'validity_text' => $this->coupon_validity_text ]; + + if (isset($this->isUsed)){ + return array_merge($coupon, [ + 'used' => $this->isUsed, + ]); + } + return $coupon; } private function returnUrl($assets) diff --git a/app/Models/Basket.php b/app/Models/Basket.php new file mode 100644 index 0000000..84daa62 --- /dev/null +++ b/app/Models/Basket.php @@ -0,0 +1,41 @@ +belongsTo(Customers::class); + } + + /** + * Campagne model relation + * @return BelongsTo + */ + public function campagne() + { + return $this->belongsTo(Campagne::class); + } + + /** + * BasketContent model relation + * @return HasMany + */ + public function contents() + { + return $this->hasMany(BasketContent::class); + } +} diff --git a/app/Models/BasketContent.php b/app/Models/BasketContent.php new file mode 100644 index 0000000..258f408 --- /dev/null +++ b/app/Models/BasketContent.php @@ -0,0 +1,31 @@ +hasOne(Basket::class); + } + + /** + * Coupon model relation + * @return HasOne + */ + public function coupon() + { + return $this->hasOne(Coupon::class, 'coupon_id', 'coupon_id'); + } +} diff --git a/app/Models/Customers.php b/app/Models/Customers.php index cfcaf1f..f64ddf6 100644 --- a/app/Models/Customers.php +++ b/app/Models/Customers.php @@ -70,4 +70,13 @@ class Customers extends Model { return $this->hasMany(PrintedWalls::class, 'customer_id', 'id'); } + + /** + * Basket model relation + * @return HasMany + */ + public function baskets() + { + return $this->hasMany(Basket::class, 'customer_id', 'id'); + } } diff --git a/app/Notification.php b/app/Notification.php index 5a9cecf..674bea6 100644 --- a/app/Notification.php +++ b/app/Notification.php @@ -21,6 +21,8 @@ class Notification extends Model */ public $timestamps = false; + protected $fillable = ['notif_name', 'notif_email', 'notif_campId']; + /** * ManyToOne * Notification -> Campagne diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 84c0428..7971bf7 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -12,7 +12,7 @@ class AuthServiceProvider extends ServiceProvider * @var array */ protected $policies = [ - 'App\Model' => 'App\Policies\ModelPolicy', + // 'App\Model' => 'App\Policies\ModelPolicy', ]; /** diff --git a/app/Traits/CouponTrait.php b/app/Traits/CouponTrait.php index 4644353..68e1c20 100644 --- a/app/Traits/CouponTrait.php +++ b/app/Traits/CouponTrait.php @@ -6,8 +6,10 @@ use App\Campagne; use App\Coupon; use App\Contact; use App\Calendrier; +use App\Enums\CampaignTypes; use App\Models\Customers; use App\Models\PrintedWalls; +use App\Mouvement; use App\Mur; use App\Panier; use App\PanierContent; @@ -147,22 +149,28 @@ trait CouponTrait public function isAvailableToPrint(Coupon $coupon, Campagne $campagne) { - $availableToPrint = true; - // ---- Contrôle de la publication - if ($coupon->coupon_published == 0) { - $availableToPrint = false; + /** + * Contrôle de la publication de la campagne + */ + if ($campagne->camp_published === 0) { + return false; + } + + // ---- Contrôle de la publication du coupon + if ($coupon->coupon_published === 0) { + return false; } // ---- Contrôle la date de début de diffusion - if ($coupon->coupon_mindatediffusion != null) { + if ($coupon->coupon_mindatediffusion !== null) { if (strtotime(date('Y-m-d')) < strtotime($coupon->coupon_mindatediffusion)) { - $availableToPrint = false; + return false; } } // ---- Contrôle la date de validité - if ($coupon->coupon_maxdatediffusion != null) { + if ($coupon->coupon_maxdatediffusion !== null) { if (strtotime(date('Y-m-d')) > strtotime($coupon->coupon_maxdatediffusion)) { - $availableToPrint = false; + return false; } } @@ -176,31 +184,25 @@ trait CouponTrait } } if (!$pub) { - $availableToPrint = false; + return false; } } - if ($campagne->camp_published == 0) { - $availableToPrint = false; - } - - return $availableToPrint; + return true; } public function isPausedToPrint(Coupon $coupon, Campagne $campagne) { - $availableToPrint = true; - // ---- Contrôle la date de début de diffusion - if ($coupon->coupon_mindatediffusion != null) { + if ($coupon->coupon_mindatediffusion !== null) { if (strtotime(date('Y-m-d')) < strtotime($coupon->coupon_mindatediffusion)) { - $availableToPrint = false; + return false; } } // ---- Contrôle la date de validité - if ($coupon->coupon_maxdatediffusion != null) { + if ($coupon->coupon_maxdatediffusion !== null) { if (strtotime(date('Y-m-d')) > strtotime($coupon->coupon_maxdatediffusion)) { - $availableToPrint = false; + return false; } } @@ -214,11 +216,11 @@ trait CouponTrait } } if (!$pub) { - $availableToPrint = false; + return false; } } - return $availableToPrint; + return true; } @@ -263,28 +265,22 @@ trait CouponTrait public function getFullQuantity(Coupon $coupon) { - $mouvements = $coupon->mouvements()->get(); - $nombre = array(); - foreach ($mouvements as $mouvement) { - array_push($nombre, $mouvement->mouvement_nombre); - } - return array_sum($nombre); + return (int)Mouvement::where('mouvement_couponId', $coupon->coupon_id)->sum('mouvement_nombre'); } public function getUsedQuantity(Coupon $coupon) { $countPrinted = 0; - $couponId = $coupon->coupon_id; - $campagnes = $coupon->campagnes()->get(); - foreach ($campagnes as $campagne) { - if ($campagne->camp_type == 0 || $campagne->camp_type == 2) { -// Murs - $murs = Mur::where(['mur_campid' => $campagne->camp_id, 'mur_couponId' => $coupon->coupon_id])->get(); - $countPrinted = $countPrinted + count($murs); + foreach ($coupon->campagnes as $campagne) { + if ($campagne->camp_type->equals(CampaignTypes::mur()) || $campagne->camp_type->equals(CampaignTypes::murss())) { + // Murs + $murs = Mur::where(['mur_campid' => $campagne->camp_id, 'mur_couponId' => $coupon->coupon_id])->count(); + $countPrinted += $murs; } else { -// Paniers - $campagneId = $campagne->camp_id; - $countPrinted = PanierContent::where('panier_content_couponId', $couponId)->whereHas('panier', function ($query) use ($campagneId) { + // Paniers + // TODO : Revoir la requete + $campagneId = $campagne->camp_id; + $countPrinted = PanierContent::where('panier_content_couponId', $coupon->coupon_id)->whereHas('panier', function ($query) use ($campagneId) { $query->where('panier_campId', $campagneId); })->sum('panier_content_printed'); } @@ -295,17 +291,15 @@ trait CouponTrait public function getDiffusedQuantity(Coupon $coupon) { $countPrinted = 0; - $couponId = $coupon->coupon_id; - $campagnes = $coupon->campagnes()->get(); - foreach ($campagnes as $campagne) { - if ($campagne->camp_type == 0 || $campagne->camp_type == 2) { -// Murs - $murs = Mur::where(['mur_campid' => $campagne->camp_id, 'mur_couponId' => $coupon->coupon_id])->get(); - $countPrinted = $countPrinted + count($murs); + foreach ($coupon->campagnes as $campagne) { + if ($campagne->camp_type->equals(CampaignTypes::mur()) || $campagne->camp_type->equals(CampaignTypes::murss())) { + // Murs + $murs = Mur::where(['mur_campid' => $campagne->camp_id, 'mur_couponId' => $coupon->coupon_id])->count(); + $countPrinted += $murs; } else { -// Paniers + // Paniers $campagneId = $campagne->camp_id; - $panierContents = PanierContent::where('panier_content_couponId', $couponId)->whereHas('panier', function ($query) use ($campagneId) { + $panierContents = PanierContent::where('panier_content_couponId', $coupon->coupon_id)->whereHas('panier', function ($query) use ($campagneId) { $query->where('panier_campId', $campagneId); })->get(); $panierContentCountPrinted = $panierContents->sum('panier_content_printed'); @@ -323,7 +317,7 @@ trait CouponTrait $enddate = date('Y-m-d H:i:s', strtotime($enddate)); $campagnes = $coupon->campagnes()->get(); foreach ($campagnes as $campagne) { - if ($campagne->camp_type == 0 || $campagne->camp_type == 2) { + if ($campagne->camp_type->equals(CampaignTypes::mur()) || $campagne->camp_type->equals(CampaignTypes::murss())) { // Murs $murs = Mur::where(['mur_campid' => $campagne->camp_id, 'mur_couponId' => $coupon->coupon_id])->whereBetween('mur_createdate', [$startdate, $enddate])->get(); $countPrinted = $countPrinted + count($murs); diff --git a/app/Traits/GenerateBR.php b/app/Traits/GenerateBR.php index 9c50be4..45a80e2 100644 --- a/app/Traits/GenerateBR.php +++ b/app/Traits/GenerateBR.php @@ -18,8 +18,8 @@ trait GenerateBR $hand_picture = 'build/assets/images/coupons/hand.png'; $mask_img = 'build/assets/images/coupons/mask.png'; - if (null !== $request->input('site_id')) { - $site = Site::where(['id' => $request->input('site_id')])->first(); + if (!is_null($request->site_id)) { + $site = Site::where(['id' => $request->site_id])->first(); } else { $site = $coupon->site()->first(); } @@ -31,32 +31,10 @@ trait GenerateBR $mask = imagecreatefrompng($mask_img); list($maskWidth, $maskHeight) = getimagesize($mask_img); - // ---- Contact distributeur - $text = $coupon->coupon_site_contact; - if (strlen($text) < 30) { - $ImageFond = imagecreate(165, 27); - $box1 = imagettfbbox(8, 0, $arial, $text); - $white = imagecolorallocate($ImageFond, 255, 255, 255); - $black = imagecolorallocate($ImageFond, 0, 0, 0); - $this->imagettftextSp($ImageFond, 8, 0, 5, 23, $black, $arial, $text, 0.01); - imagecolortransparent($ImageFond, $white); - imagecopy($mask, $ImageFond, 743, 365, 0, 0, imagesx($ImageFond), imagesy($ImageFond)); - } else { - $ImageFond = imagecreate(195, 27); - $box1 = imagettfbbox(7, 0, $arial, $text); - $white = imagecolorallocate($ImageFond, 255, 255, 255); - $black = imagecolorallocate($ImageFond, 0, 0, 0); - $this->imagettftextSp($ImageFond, 7, 0, 5, 23, $black, $arial, $text, 0); - imagecolortransparent($ImageFond, $white); - imagecopy($mask, $ImageFond, 713, 365, 0, 0, imagesx($ImageFond), imagesy($ImageFond)); - } - - // ----- Mentions légales + RCS if ($coupon->coupon_legal_mentions_splitted == 0) { $txt_mentions = $coupon->coupon_legal_mentions . " " . $coupon->coupon_rcs; $ImageMentionFond = imagecreate(415, 100); - $boxMentions = imagettfbbox(7, 0, $arial, $txt_mentions); $white = imagecolorallocate($ImageMentionFond, 255, 255, 255); imagefilledrectangle($ImageMentionFond, 0, 0, 414, 99, $white); $black = imagecolorallocate($ImageMentionFond, 0, 0, 0); @@ -66,8 +44,17 @@ trait GenerateBR $this->imagettftextSp($ImageMentionFond, 7, 0, 15, $y, $black, $arial, $line, 0.01); $y = $y + 9; } - - // imagecolortransparent($ImageMentionFond, $white); + $this->imagettftextSp( + $ImageMentionFond, + 7, + 0, + 15, + $y, + $black, + $arialbd, + sprintf("%s : %s", __('Renseignements'), $coupon->coupon_site_contact), + 0.01 + ); imagecopy($mask, $ImageMentionFond, 335, 235, 0, 0, imagesx($ImageMentionFond), imagesy($ImageMentionFond)); } @@ -86,9 +73,9 @@ trait GenerateBR $black = imagecolorallocate($ImageFond, 0, 0, 0); $blue = imagecolorallocate($ImageFond, 8, 169, 223); $grey = imagecolorallocate($ImageFond, 219, 219, 221); - $this->imagettftext_contour($ImageFond, 95, 0, 5, 110, $grey, $impact, $textMontant, $blue, 3, 3, 3, 3); - $this->imagettftext_contour($ImageFond, 50, 0, 220, 55, $grey, $arial, $textEuro, $blue, 2, 2, 2, 2); - $this->imagettftext_contour($ImageFond, 16, 0, 0, 150, $grey, $impact, $libReduc, $blue, 1, 1, 1, 1); + $this->imagettftextContour($ImageFond, 95, 0, 5, 110, $grey, $impact, $textMontant, $blue, 3, 3, 3, 3); + $this->imagettftextContour($ImageFond, 50, 0, 220, 55, $grey, $arial, $textEuro, $blue, 2, 2, 2, 2); + $this->imagettftextContour($ImageFond, 16, 0, 0, 150, $grey, $impact, $libReduc, $blue, 1, 1, 1, 1); imagecolortransparent($ImageFond, $white); imagecopy($mask, $ImageFond, 360, 10, 0, 0, imagesx($ImageFond), imagesy($ImageFond)); @@ -107,13 +94,11 @@ trait GenerateBR $textFiliLines = explode("\r\n", $textFili); $ImageFond = imagecreate(402, 240); $white = imagecolorallocate($ImageFond, 255, 255, 255); - $blackOpac = imagecolorallocatealpha($ImageFond, 0, 0, 0, 20); $y = 0; foreach ($textFiliLines as $textFiliLine) { $y = $y + 20; $this->imagettftextSp($ImageFond, 11, 25, -100, $y, $black, $arial, $textFiliLine, 0.01); } - // imagettftext($ImageFond , 11 , 25, -100, 11, $blackOpac, $arial, $textReduc); imagecolortransparent($ImageFond, $white); imagecopymerge($mask, $ImageFond, 349, 1, 0, 0, imagesx($ImageFond), imagesy($ImageFond), 30); @@ -159,7 +144,7 @@ trait GenerateBR imagecolortransparent($ImageFond, $white); imagecopy($mask, $ImageFond, 360, 170, 0, 0, imagesx($ImageFond), imagesy($ImageFond)); }//end if - // imagettftext($ImageFond , 12 , 0, 0, 14, $black, $arialbd, $textReduc); + // ---- Copie le produit dans le mask $size = getimagesize($product_image); switch ($size['mime']) { @@ -221,7 +206,7 @@ trait GenerateBR - function imagettftext_contour($image, $fontsize, $angle, $x, $y, $color, $font, $text, $color_contour, $contour_haut = 1, $contour_bas = 1, $contour_gauche = 1, $contour_droite = 1) + function imagettftextContour($image, $fontsize, $angle, $x, $y, $color, $font, $text, $color_contour, $contour_haut = 1, $contour_bas = 1, $contour_gauche = 1, $contour_droite = 1) { $coords_x = array(); $coords_y = array(); @@ -417,6 +402,7 @@ trait GenerateBR // imagecolortransparent($ImageMentionFond, $white); imagecopy($mask, $ImageMentionFond, 335, 235, 0, 0, imagesx($ImageMentionFond), imagesy($ImageMentionFond)); } else { + // FIXME : Semble non utilise ici // FR $txt_mentions = $legalMentions[0] . " " . $coupon->coupon_rcs; $ImageMentionFond = imagecreate(415, 100); @@ -438,7 +424,15 @@ trait GenerateBR // ---- Inscription de l'utilisateur et de la ville $libelle = "OFFRE RÉSERVÉE À"; - $user = $crmInfos->firstname . " " . $crmInfos->lastname . "\r\n" . $crmInfos->postcode . " " . $crmInfos->city; + $user = sprintf( + "%s %s\r\n%s\r\n%s %s\r\n%s", + $crmInfos->firstname, + $crmInfos->lastname, + $crmInfos->address, + $crmInfos->postcode, + $crmInfos->city, + sprintf("id %s", $crmInfos->crm_id) + ); $ImageFond = imagecreate(120, 40); $white = imagecolorallocate($ImageFond, 255, 255, 255); $black = imagecolorallocate($ImageFond, 0, 0, 0); @@ -447,7 +441,7 @@ trait GenerateBR imagettftext($ImageFond, 8, 0, (120 / 2) - (abs($box2[4]) - abs($box2[6])) / 2, 10, $black, $arialbd, $libelle); imagettftext($ImageFond, 8, 0, (120 / 2) - (abs($box1[4]) - abs($box1[6])) / 2, 25, $black, $arial, $user); imagecolortransparent($ImageFond, $white); - imagecopy($mask, $ImageFond, 770, 50, 0, 0, imagesx($ImageFond), imagesy($ImageFond)); + imagecopy($mask, $ImageFond, 770, 40, 0, 0, imagesx($ImageFond), imagesy($ImageFond)); // ---- Inscription de la date de validité $libelle = "Valable jusqu'au :"; @@ -467,6 +461,21 @@ trait GenerateBR imagecolortransparent($ImageFond, $white); imagecopy($mask, $ImageFond, 780, 142, 0, 0, imagesx($ImageFond), imagesy($ImageFond)); + // Right barcode + $codeBarre = "0000000000001"; // FIXME : put real id here + $generator = new \Picqer\Barcode\BarcodeGeneratorPNG(); + $base64 = $generator->getBarcode($codeBarre, $generator::TYPE_CODE_128_A, 2, 46); + $barcodeImage = imagecreatefromstring($base64); + imagecopy($mask, $barcodeImage, 540, 342, 0, 0, imagesx($barcodeImage), imagesy($barcodeImage)); + // Text + $ImageFond = imagecreate(380, 15); + $white = imagecolorallocate($ImageFond, 255, 255, 255); + $black = imagecolorallocate($ImageFond, 0, 0, 0); + imagettftext($ImageFond, 11, 0, 0, 14, $black, $arial, $codeBarre); + imagecopy($mask, $ImageFond, 795, 390, 0, 0, imagesx($ImageFond), imagesy($ImageFond)); + // End text + // End right barcode + // ---- Création code barre if ($customer->country !== 250) { $textReduc = "Traitement ScanCoupon/HighCo Data Benelux"; @@ -486,7 +495,7 @@ trait GenerateBR imagecopy($mask, $ImageFond, 52, 322, 0, 0, imagesx($ImageFond), imagesy($ImageFond)); } - + // Left barcode if ($customer->country !== 250) { $codeBarre = imagecreatefromjpeg($coupon->coupon_gs1code); imageresolution($codeBarre, 72); @@ -506,22 +515,18 @@ trait GenerateBR imagecopy($mask, $test, 60, 250, 0, 0, imagesx($test), imagesy($test)); } else { - $codeBarre = $coupon->coupon_code . strtoupper($customer->crm_id); - $codeBarrePrint = $codeBarre; - if (strpos($customer->crm_id, "@") !== false) { - $codeBarre = $coupon->coupon_code . strtolower($customer->crm_id); - $codeBarrePrint = $coupon->coupon_code . ".@."; - } + $codeBarre = $coupon->coupon_code; $generator = new \Picqer\Barcode\BarcodeGeneratorPNG(); - $base64 = $generator->getBarcode($codeBarrePrint, $generator::TYPE_CODE_128_A, 2, 46); + $base64 = $generator->getBarcode($codeBarre, $generator::TYPE_CODE_128_A, 2, 46); $barcodeImage = imagecreatefromstring($base64); - imagecopy($mask, $barcodeImage, 52, 342, 0, 0, imagesx($barcodeImage), imagesy($barcodeImage)); + imagecopy($mask, $barcodeImage, 10, 342, 0, 0, imagesx($barcodeImage), imagesy($barcodeImage)); $ImageFond = imagecreate(380, 15); $white = imagecolorallocate($ImageFond, 255, 255, 255); $black = imagecolorallocate($ImageFond, 0, 0, 0); imagettftext($ImageFond, 11, 0, 0, 14, $black, $arial, $codeBarre); imagecopy($mask, $ImageFond, 52, 390, 0, 0, imagesx($ImageFond), imagesy($ImageFond)); }//end if + // End left barcode ob_start(); imagejpeg($mask, null, 100); diff --git a/composer.json b/composer.json index b6736ae..751bea0 100644 --- a/composer.json +++ b/composer.json @@ -14,11 +14,12 @@ "barryvdh/laravel-dompdf": "^0.9.0", "doctrine/dbal": "^2.8", "fideloper/proxy": "^4.0", + "kwaadpepper/enum": "^2.0", "laravel/framework": "^8.0", "laravel/tinker": "^2.0", "laravel/ui": "^3.0", "laravelcollective/html": "6.*", - "ldc/ldccrmv2client": "^3.0", + "ldc/ldccrmv2client": "^3.0.5", "picqer/php-barcode-generator": "^0.3.0", "sentry/sentry-laravel": "^4.13", "szymach/c-pchart": "^3.0", diff --git a/config/app.php b/config/app.php index 0195c29..4b01574 100644 --- a/config/app.php +++ b/config/app.php @@ -213,6 +213,9 @@ return [ 'Html' => Collective\Html\HtmlFacade::class, 'Debugbar' => Barryvdh\Debugbar\Facade::class, 'PDF' => Barryvdh\DomPDF\Facade::class, + 'CampaignTypes' => \App\Enums\CampaignTypes::class, + 'Countries' => \App\Enums\Countries::class, + 'UserRoles' => \App\Enums\UserRoles::class, ], ]; diff --git a/package.json b/package.json index 8444e3b..305947a 100644 --- a/package.json +++ b/package.json @@ -35,5 +35,8 @@ "sweetalert": "^2.0.8", "vue": "^2.5.7", "webpack-cli": "^6.0.1" + }, + "dependencies": { + "node-sass": "^6.0.1" } } diff --git a/resources/assets/images/coupons/mask.png b/resources/assets/images/coupons/mask.png index 34c6d6f9fae06b3b9ab4278f87332d1aeb891400..b3d79b632800a73891a592e54f6ab01fe63f61e0 100755 GIT binary patch literal 69073 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 yS(_5afE&}TwGsKHO7GEF9 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

@MhY*{}@eSNp{F_M1FX<{RsZ+S}i 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 WD)>-1K2RX|cyu%j)az95NBtj%OZfQ! literal 71844 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

o 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 diff --git a/resources/js/ajax.js b/resources/js/ajax.js index f9fed68..961ae6a 100644 --- a/resources/js/ajax.js +++ b/resources/js/ajax.js @@ -6,7 +6,7 @@ $('#addCoupons').on('click', function(event){ if($(this).is(':checked')){ ids.push($(this).val()); } - + }) $.ajaxSetup({ headers: { @@ -15,14 +15,13 @@ $('#addCoupons').on('click', function(event){ }); $.ajax({ - url: '/add-coupons-ajax', + url: '/bo/campagne/'+ campid +'/add-coupons', type: 'POST', data: { - ids: ids , - campid: campid + ids: ids }, dataType: 'JSON', - success: function (reponse) { + success: function (reponse) { $.each(reponse, function(index, coupon){ var html = '

'+coupon.coupon_code+'
- - - - - - @foreach($couponsLinked as $coupon) - - - - - - @endforeach -
CodeNom
{{ $coupon->coupon_code }}{{ $coupon->coupon_name }}camp_id}} data-couponid={{$coupon->coupon_id}}>
- @if(count($couponsLinked) == 0) -

Pas de coupons liés

- @endif - - - -
-

Notifications par mail

-
- {!! Form::open(['action' => 'CampagneController@saveNotification']) !!} -
-
- {!! Form::label('notif_name', 'Nom') !!} - {!! Form::input('text', 'notif_name', null, ['required']) !!} - {{ Form::hidden('notif_campId', $campagne->camp_id) }} -
-
-
- {!! Form::label('notif_email', 'Email') !!} - {!! Form::input('text', 'notif_email', null, ['required']) !!} -
-
- - {!! Form::submit('Ajouter') !!} -
-
- {!! Form::close() !!} -
- - - - - - - - - - - @foreach($campagne->notifications as $n) - - - - - - - @endforeach - -
{{__('No') }}{{__('Nom') }}{{__('Email') }}
{{ $n->notif_id }}{{ $n->notif_name }}{{ $n->notif_email }}
-
- -{!! Form::open(['id' => 'delete-hidden']) !!} - @method('DELETE') -{!! Form::close() !!} -@endsection diff --git a/resources/views/campagnes/create.blade.php b/resources/views/campagnes/create.blade.php new file mode 100644 index 0000000..5f6c97c --- /dev/null +++ b/resources/views/campagnes/create.blade.php @@ -0,0 +1,55 @@ +@extends('layouts.main') + +@section('content') +

+ + + + {{ __('Nouvelle campagne') }} +

+
+ @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif +
+ @csrf +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + + +
+
+
+ +
+
+
+@endsection diff --git a/resources/views/campagnes/edit.blade.php b/resources/views/campagnes/edit.blade.php new file mode 100644 index 0000000..5da7161 --- /dev/null +++ b/resources/views/campagnes/edit.blade.php @@ -0,0 +1,147 @@ +@extends('layouts.main') + +@section('content') +

+ + + + {{ __('Editer la campagne') }} +

+ +
+ @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif +
+ @csrf + @method('PUT') +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + camp_limitedPrinting === 1 ? 'checked="checked"' : '' }} name="camp_limitedPrinting" type="checkbox" value="{{ $campagne->camp_limitedPrinting }}"> + +
+
+
+ +
+
+
+
+

+ + {{ __('Coupons de la campagne') }} +

+ + + + + + + + @foreach($campagne->coupons as $coupon) + + + + + + @endforeach +
{{ __('Code') }}{{ __('Nom') }}
{{ $coupon->coupon_code }}{{ $coupon->coupon_name }}camp_id}} data-couponid={{$coupon->coupon_id}}>
+ @if(count($campagne->coupons) === 0) +

{{ __('Pas de coupons liés') }}

+ @endif + + +
+
+

{{ __('Notifications par mail') }}

+
+
+ @csrf +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+
+ + + + + + + + + + + @foreach($campagne->notifications as $n) + + + + + + + @endforeach + +
{{__('No') }}{{__('Nom') }}{{__('Email') }}
{{ $n->notif_id }}{{ $n->notif_name }}{{ $n->notif_email }}
+
+ +
+ @csrf + @method('DELETE') +
+@endsection diff --git a/resources/views/campagne/index.blade.php b/resources/views/campagnes/index.blade.php similarity index 57% rename from resources/views/campagne/index.blade.php rename to resources/views/campagnes/index.blade.php index 7788bcf..6af8ba4 100644 --- a/resources/views/campagne/index.blade.php +++ b/resources/views/campagnes/index.blade.php @@ -1,7 +1,16 @@ @extends('layouts.main') @section('content') -

Campagnes Ajouter

+

+ + + + {{ __('Campagnes') }} + + + {{ __('Ajouter') }} + +

@@ -19,49 +28,52 @@ - + @if ( $c->camp_published === 1) @else @endif - - + + @endforeach
{{ $c->camp_id }} {{ $c->camp_name }}{{ $c->camp_createdate }}{{ date('d/m/Y H:i', strtotime($c->camp_createdate)) }} + @if ( $c->camp_published === 1) + + @endif + + @if ($c->camp_published === 0) + + @endif +
-
-
- -
-{!! Form::open(['id' => 'delete-hidden']) !!} +
+ @csrf @method('DELETE') -{!! Form::close() !!} +
+ @endsection @section('scripts') @endsection -@if(isset($created) && $created) - @section('scripts') - - @endsection - @endif diff --git a/resources/views/contact/index.blade.php b/resources/views/contact/index.blade.php index 404f0eb..9755fa8 100644 --- a/resources/views/contact/index.blade.php +++ b/resources/views/contact/index.blade.php @@ -2,46 +2,69 @@ @section('content') -

Recherche de contacts Ajouter

+

+ + {{ __('Recherche de contacts') }} + + + {{ __('Ajouter') }} + +

+ @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif
- +
- +
- +
- +
- +
- +
- +
@@ -50,28 +73,17 @@
- - - - - - + + + + + + @foreach ($contacts as $contact) @php - switch ($contact->contact_country) { - case 250: - $contact->contact_country = 'France'; - break; - case 56: - $contact->contact_country = 'Belgique'; - break; - case 442: - $contact->contact_country = 'Luxembourg'; - break; - default: - $contact->contact_country = 'France'; - break; + if (!in_array($contact->contact_country, Countries::toValues())) { + $contact->contact_country = 250; } @endphp @@ -80,7 +92,7 @@ - + @endforeach diff --git a/resources/views/coupon/create.blade.php b/resources/views/coupon/create.blade.php index 5ff0f60..9084c7d 100644 --- a/resources/views/coupon/create.blade.php +++ b/resources/views/coupon/create.blade.php @@ -11,186 +11,192 @@ @endif
-

Nouveau Coupon

- {!! Form::open(['route' => 'coupon.store', 'enctype' => 'multipart/form-data']) !!} +

+ + {{ __('Nouveau Coupon') }} +

+
+ @csrf
- {!! Form::label('coupon_code', 'Code de traitement *') !!} - {!! Form::input('text', 'coupon_code', null, ['required']) !!} + +
- {!! Form::label('coupon_name', 'Nom interne *') !!} - {!! Form::input('text', 'coupon_name', null, ['required']) !!} + +
- {!! Form::label('coupon_value', 'Valeur faciale *') !!} - {!! Form::number('coupon_value', null, ['step' => '0.05', 'required']) !!} + +
- {!! Form::label('initial_quantity', 'Quantité initiale *') !!} - {!! Form::text('initial_quantity', null, ['required']) !!} + +
- {!! Form::label('coupon_norme', 'Activer la Norme GS1') !!} - {!! Form::checkbox('coupon_norme', 1, false, array('id'=>'coupon_norme')) !!} - {!! Form::label('coupon_norme', 'Activer') !!} + + +
- {!! Form::label('coupon_seuil_alert', 'Seuil d\'alerte *') !!} - {!! Form::number('coupon_seuil_alert', 100, ['required']) !!} + +
- {!! Form::label('coupon_enablelimitquantity', 'Activer La limite de quantité') !!} - {!! Form::checkbox('coupon_enablelimitquantity', 1, false, array('id'=>'coupon_enablelimitquantity')) !!} - {!! Form::label('coupon_enablelimitquantity', 'Activer') !!} + + +
- {!! Form::label('coupon_enablelimitperiod', 'Activer la limite sur une période') !!} - {!! Form::checkbox('coupon_enablelimitperiod', 1, false, array('id'=>'coupon_enablelimitperiod')) !!} - {!! Form::label('coupon_enablelimitperiod', 'Activer') !!} + + +
- {!! Form::label('coupon_limitquantity', 'Limiter la diffusion à') !!} + - {!! Form::number('coupon_limitquantity', null, ['class'=>'small']) !!} coupons + {{ __('coupons') }}
- {!! Form::label('coupon_limitperiod', 'Limiter la diffusion à une offre tous les') !!} + - {!! Form::number('coupon_limitperiod', null, ['class'=>'small']) !!} jours + {{ __('jours') }}
-

Validité

+

{{ __('Validité') }}

- {!! Form::label('coupon_mindatediffusion', 'Date de début de diffusion') !!} - {!! Form::date('coupon_mindatediffusion') !!} + +
- {!! Form::label('coupon_maxdatediffusion', 'Date de fin de diffusion') !!} - {!! Form::date('coupon_maxdatediffusion') !!} + +
-
- {!! Form::label('coupon_validity', 'Durée de validité à l\'impression') !!} - - {!! Form::number('coupon_validity', 30, ['class'=>'small', 'required']) !!} jours - -
+ + + {{ __('jours') }} + +
- {!! Form::label('coupon_maxdatevalidity', 'Date de fin de validité') !!} - {!! Form::date('coupon_maxdatevalidity') !!} + +
-

Calendrier de diffusion

+

{{ __('Calendrier de diffusion') }}

-

Vignettes

+

{{ __('Vignettes') }}

- {!! Form::label('coupon_thumbnail1', 'Vignette Desktop active') !!} - {!! Form::file('coupon_thumbnail1', ['class' => 'input-file']) !!} + +
- {!! Form::label('coupon_thumbnail2', 'Vignette Desktop grise') !!} - {!! Form::file('coupon_thumbnail2', ['class' => 'input-file']) !!} + +
- {!! Form::label('coupon_thumbnail3', 'Vignette Mobile active') !!} - {!! Form::file('coupon_thumbnail3', ['class' => 'input-file']) !!} + +
- {!! Form::label('coupon_thumbnail4', 'Vignette Mobile grise') !!} - {!! Form::file('coupon_thumbnail4', ['class' => 'input-file']) !!} + +
-

Informations pour génération

- {{ __('Informations pour génération') }} +
- {!! Form::label('coupon_treatment_center', 'Centre de traitement *') !!} - {!! Form::select('coupon_treatment_center', ['HighCo', 'Sogec']) !!} + +
- {!! Form::label('coupon_legal_mentions_splitted', 'Activer les mentions internationnales') !!} - {!! Form::checkbox('coupon_legal_mentions_splitted', 1, false) !!} - {!! Form::label('coupon_legal_mentions_splitted', 'Activer') !!} + + +
- {!! Form::label('coupon_validity_text', 'Texte de validité *') !!} - {!! Form::textarea('coupon_validity_text', null, ['required']) !!} + +
- {!! Form::label('coupon_legal_mentions', 'Mentions légales *') !!} - {!! Form::textarea('coupon_legal_mentions', $mentions, ['required']) !!} + +
- {!! Form::label('coupon_decoration_image', 'Image de décoration *') !!} - (280x260px ou 280x160px si GS1) - Formats : JPG ou PNG + + {{ __('280x260px ou 280x160px si GS1') }} + {{ __('Formats : JPG ou PNG') }}
- {!! Form::file('coupon_decoration_image', ['class' => 'input-file', 'required', 'accept' => '.jpg,.jpeg,.png']) !!} +
- {!! Form::label('coupon_logo_image', 'Logo à afficher *') !!} - 135x70px - Formats : JPG ou PNG + + {{ __('135x70px') }} + {{ __('Formats : JPG ou PNG') }}
- {!! Form::file('coupon_logo_image', ['class' => 'input-file', 'required']) !!} +
- {!! Form::label('coupon_rcs', 'RCS du site *') !!} - {!! Form::text('coupon_rcs', $site->site_rcs, ['required']) !!} + +
- {!! Form::label('coupon_site_contact', 'Contact mail *') !!} - {!! Form::text('coupon_site_contact', $site->site_contact, ['required']) !!} + +
- {!! Form::label('coupon_gs1code', 'Image du code barre GS1 *') !!} - Formats : JPG + + {{ __('Formats : JPG') }}
- {!! Form::file('coupon_gs1code', ['class' => 'input-file']) !!} +
- {!! Form::label('coupon_published', 'Publié ?') !!} - {!! Form::checkbox('coupon_published', 1, false, array('id'=>'coupon_published')) !!} - {!! Form::label('coupon_published', 'Activer') !!} + + +
- - {!! Form::hidden('site_id', session()->get('site')->id) !!} + +
- Les champs avec un * sont obligatoires - {!! Form::submit('Créer coupon') !!} + {{ __('Les champs avec un * sont obligatoires') }} +
- {!! Form::close() !!} + @endsection diff --git a/resources/views/coupon/edit.blade.php b/resources/views/coupon/edit.blade.php index 4dc75b9..47991dc 100644 --- a/resources/views/coupon/edit.blade.php +++ b/resources/views/coupon/edit.blade.php @@ -11,90 +11,94 @@ @endif
-

Editer le Coupon

- {!! Form::model($coupon, ['route' => ['coupon.update', $coupon], 'enctype' => 'multipart/form-data']) !!} +

+ + {{ __('Editer le Coupon')}} +

+
+ @csrf @method('PUT')
- {!! Form::label('coupon_code', 'Code de traitement *') !!} - {!! Form::input('text', 'coupon_code', $coupon->coupon_code, ['required']) !!} + +
- {!! Form::label('coupon_name', 'Nom interne *') !!} - {!! Form::input('text', 'coupon_name', $coupon->coupon_name, ['required']) !!} + +
- {!! Form::label('coupon_value', 'Valeur faciale *') !!} - {!! Form::number('coupon_value', null, ['step' => '0.05', 'required']) !!} + +
- {!! Form::label('coupon_norme', 'Activer la Norme GS1') !!} - {!! Form::checkbox('coupon_norme', 1, ($coupon->coupon_norme ==1?true:null), array('id'=>'coupon_norme')) !!} - {!! Form::label('coupon_norme', 'Activer') !!} + + coupon_norme === 1 ? 'checked' : null) }}> +
- {!! Form::label('coupon_seuil_alert', 'Seuil d\'alerte *') !!} - {!! Form::number('coupon_seuil_alert', $coupon->coupon_seuil_alert, ['required']) !!} + +
- +
- {!! Form::label('coupon_enablelimitquantity', 'Activer La limite de quantité') !!} - {!! Form::checkbox('coupon_enablelimitquantity', 1, ($coupon->coupon_enablelimitquantity ==1?true:null), array('id'=>'coupon_enablelimitquantity')) !!} - {!! Form::label('coupon_enablelimitquantity', 'Activer') !!} + + coupon_enablelimitquantity === 1 ? 'checked' : null) }}> +
- {!! Form::label('coupon_enablelimitperiod', 'Activer la limite sur une période') !!} - {!! Form::checkbox('coupon_enablelimitperiod', 1, ($coupon->coupon_enablelimitperiod ==1?true:null), array('id'=>'coupon_enablelimitperiod')) !!} - {!! Form::label('coupon_enablelimitperiod', 'Activer') !!} + + coupon_enablelimitperiod === 1 ? 'checked' : null) }}> +
- {!! Form::label('coupon_limitquantity', 'Limiter la diffusion à') !!} + - {!! Form::number('coupon_limitquantity', null, ['class'=>'small']) !!} coupons + {{ __('coupons') }}
- {!! Form::label('coupon_limitperiod', 'Limiter la diffusion à une offre tous les') !!} + - {!! Form::number('coupon_limitperiod', null, ['class'=>'small']) !!} jours + {{ __('jours') }}
-

Validité

+

{{ __('Validité') }}

- {!! Form::label('coupon_mindatediffusion', 'Date de début de diffusion') !!} - {!! Form::date('coupon_mindatediffusion') !!} + +
- {!! Form::label('coupon_maxdatediffusion', 'Date de fin de diffusion') !!} - {!! Form::date('coupon_maxdatediffusion') !!} + +
- {!! Form::label('coupon_validity', 'Durée de validité à l\'impression') !!} + - {!! Form::number('coupon_validity', null, ['class'=>'small', 'required']) !!} jours + {{ __('jours') }}
- {!! Form::label('coupon_maxdatevalidity', 'Date de fin de validité') !!} - {!! Form::date('coupon_maxdatevalidity') !!} + +
-

Calendrier de diffusion

+

{{ __('Calendrier de diffusion') }}

@foreach ($coupon->calendriers()->get() as $calendrier)
@@ -103,12 +107,12 @@
@endforeach
-

Mouvements

+

{{ __('Mouvements') }}

RefNomEmailCode PostalVillePays{{ __('Ref') }}{{ __('Nom') }}{{ __('Email') }}{{ __('Code Postal') }}{{ __('Ville') }}{{ __('Pays') }}
{{ $contact->contact_email }} {{ $contact->contact_cp }} {{ $contact->contact_ville }}{{ $contact->contact_country }}{{ Countries::make($contact->contact_country)->label }}
- - + + @foreach ($coupon->mouvements()->get() as $mouvement) @@ -126,136 +130,138 @@
DateQuantité{{ __('Date') }}{{ __('Quantité') }}
-

Vignettes

+

{{ __('Vignettes') }}

- {!! Form::label('coupon_thumbnail1', 'Vignette Desktop active') !!} - {!! Form::file('coupon_thumbnail1', ['class' => 'input-file']) !!} + + @if($coupon->coupon_thumbnail1 != "")
{{ $coupon->coupon_name }}
@endif - {!! Form::input('hidden', 'coupon_thumbnail1_hidden', $coupon->coupon_thumbnail1) !!} +
- {!! Form::label('coupon_thumbnail2', 'Vignette Desktop grise') !!} - {!! Form::file('coupon_thumbnail2', ['class' => 'input-file']) !!} + + @if($coupon->coupon_thumbnail2 != "")
{{ $coupon->coupon_name }}
@endif - {!! Form::input('hidden', 'coupon_thumbnail2_hidden', $coupon->coupon_thumbnail2) !!} +
- {!! Form::label('coupon_thumbnail3', 'Vignette Mobile active') !!} - {!! Form::file('coupon_thumbnail3', ['class' => 'input-file']) !!} + + @if($coupon->coupon_thumbnail3 != "")
{{ $coupon->coupon_name }}
@endif - {!! Form::input('hidden', 'coupon_thumbnail3_hidden', $coupon->coupon_thumbnail3) !!} +
- {!! Form::label('coupon_thumbnail4', 'Vignette Mobile grise') !!} - {!! Form::file('coupon_thumbnail4', ['class' => 'input-file']) !!} + + @if($coupon->coupon_thumbnail4 != "")
{{ $coupon->coupon_name }}
@endif - {!! Form::input('hidden', 'coupon_thumbnail4_hidden', $coupon->coupon_thumbnail4) !!} +
-

Informations pour génération

+

{{ __('Informations pour génération') }}

- {!! Form::label('coupon_treatment_center', 'Centre de traitement *') !!} - {!! Form::select('coupon_treatment_center', ['HighCo', 'Sogec']) !!} + +
- {!! Form::label('coupon_legal_mentions_splitted', 'Activer les mentions internationnales') !!} - {!! Form::checkbox('coupon_legal_mentions_splitted', 1, ($coupon->coupon_legal_mentions_splitted ==1?true:null)) !!} - {!! Form::label('coupon_legal_mentions_splitted', 'Activer') !!} + + coupon_legal_mentions_splitted === 1 ? 'checked' : null) }}> +
- {!! Form::label('coupon_validity_text', 'Texte de validité *') !!} - {!! Form::textarea('coupon_validity_text', null, ['required']) !!} + +
- {!! Form::label('coupon_legal_mentions', 'Mentions légales *') !!} - {!! Form::textarea('coupon_legal_mentions', null, ['required']) !!} + +
- {!! Form::label('coupon_decoration_image', 'Image de décoration *') !!} - 280x260px ou 280x160px si GS1 - Formats : JPG ou PNG + + {{ __('280x260px ou 280x160px si GS1') }} + {{ __('Formats : JPG ou PNG') }}
- - {!! Form::file('coupon_decoration_image', ['class' => 'input-file', 'accept' => '.jpg,.jpeg,.png']) !!} + @if($coupon->coupon_decoration_image != "")
{{ $coupon->coupon_name }}
@endif - {!! Form::input('hidden', 'coupon_decoration_image_hidden', $coupon->coupon_decoration_image) !!} +
- {!! Form::label('coupon_logo_image', 'Logo à afficher *') !!} - 135x70px - Formats : JPG ou PNG + + {{ __('135x70px') }} + {{ __('Formats : JPG ou PNG') }}
- {!! Form::file('coupon_logo_image', ['class' => 'input-file']) !!} + @if($coupon->coupon_logo_image != "")
{{ $coupon->coupon_name }}
@endif - {!! Form::input('hidden', 'coupon_logo_image_hidden', $coupon->coupon_logo_image) !!} +
- {!! Form::label('coupon_rcs', 'RCS du site *') !!} - {!! Form::text('coupon_rcs', null, ['required']) !!} + +
- {!! Form::label('coupon_site_contact', 'Contact mail *') !!} - {!! Form::text('coupon_site_contact', null, ['required']) !!} + +
- {!! Form::label('coupon_gs1code', 'Image du code barre GS1 *') !!} - Formats : JPG + + {{ __('Formats : JPG') }}
- {!! Form::file('coupon_gs1code', ['class' => 'input-file']) !!} + @if($coupon->coupon_gs1code != "")
{{ $coupon->coupon_name }}
- {!! Form::input('hidden', 'coupon_gs1code_image_hidden', $coupon->coupon_gs1code, array('id'=>'coupon_gs1code_image_hidden')) !!} + @endif - +
- {!! Form::label('coupon_published', 'Publié ?') !!} - {!! Form::checkbox('coupon_published', 1, ($coupon->coupon_published ==1?true:null), array('id'=>'coupon_published')) !!} - {!! Form::label('coupon_published', 'Activer') !!} + + coupon_published === 1 ? 'checked' : null) }}> +
-

Bon généré :

+

{{ __('Bon généré') }} :

@if($coupon->coupon_image != "")
@@ -263,12 +269,13 @@
@endif
- {!! Form::hidden('site_id', session()->get('site')->id) !!} + +
- {!! Form::submit('Mettre à jour') !!} +
- - {!! Form::close() !!} + + @endsection diff --git a/resources/views/coupon/index.blade.php b/resources/views/coupon/index.blade.php index 1f7ea7e..1469dd2 100644 --- a/resources/views/coupon/index.blade.php +++ b/resources/views/coupon/index.blade.php @@ -1,7 +1,14 @@ @extends('layouts.main') @section('content') -

Coupons Ajouter

+

+ + {{ __('Coupons') }} + + + {{ __('Ajouter') }} + +

@@ -38,9 +45,10 @@ @endforeach
- +
-{!! Form::open(['id' => 'delete-hidden']) !!} +
+ @csrf @method('DELETE') -{!! Form::close() !!} +
@endsection diff --git a/resources/views/customers/index.blade.php b/resources/views/customers/index.blade.php new file mode 100644 index 0000000..6ce22c4 --- /dev/null +++ b/resources/views/customers/index.blade.php @@ -0,0 +1,81 @@ +@extends('layouts.main') + +@section('content') + +

+ + {{ __('Customers') }} {{ $site->site_nom }} +

+@if ($errors->any()) +
+ +
+@endif +
+
+
+ @csrf + + +
+
+ @if (!is_null($email)) + + + + + + + + @if (!count($campaigns)) + + + + @else + + + + + + @endif + +
{{ __('Email') }}{{ __('Pays') }}
{{ __('Aucun résultat.') }}
{{ $customer->email }}{{ Countries::make($customer->country)->label }}Détails
+ @dump($campaigns) + @foreach ($campaigns as $campaign) +
+

{{ $campaign->camp_name }}

+

Type : {{ $campaign->camp_type->label }}

+
+
+

Pas de contrôle à réinitialiser

+
+
+
+ @endforeach + @endif + {{--
+
+

Mur BR Le Gaulois

+

Type : Mur

+
+
+

Pas de contrôle à réinitialiser

+
+
+
+
+

OP Gauloisirs

+

Type : Panier

+
+
+ +
+
+
+
--}} +
+@endsection diff --git a/resources/views/export/index.blade.php b/resources/views/export/index.blade.php index 651ea18..2d487c3 100644 --- a/resources/views/export/index.blade.php +++ b/resources/views/export/index.blade.php @@ -1,29 +1,28 @@ @extends('layouts.main') @section('content') -

Export des données coupons

+

+ + {{ __('Export des données coupons') }} +

-

Période :

+

{{ __('Période') }} :

- - Exporter les données + + {{ __('Exporter les données') }}
@foreach ($sites as $site) -

{{ $site->site_nom }} ({{ $site->site_id }})

- @if (isset($site->coupons) && $site->coupons->count()>0) @foreach ($site->coupons as $coupon)
@endforeach - @endif - @endforeach
- Exporter les données + {{ __('Exporter les données') }}
@@ -38,15 +37,19 @@ if($(this).is(':checked')){ ids.push($(this).val()); } - - }) + + }); + if (ids.length === 0) { + alert('{{ __('Vous devez sélectionner au moins un coupon.') }}'); + return; + } $.ajaxSetup({ headers: { 'X-CSRF-TOKEN': $('#csrf').val() } }); $.ajax({ - url: '/exportCoupon', + url: '/bo/export/coupon', type: 'POST', data: { ids: ids, @@ -54,11 +57,11 @@ enddate: $('#enddate').val(), }, dataType: 'JSON', - success: function (response) { + success: function (response) { $.each(response, function(index, element){ $('.dl-pdf').attr("href", '/'+element); $('.dl-pdf').get(0).click(); - }) + }) }, error: function (e) { console.log(e.responseText); @@ -67,4 +70,4 @@ }) -@endsection \ No newline at end of file +@endsection diff --git a/resources/views/export/template.blade.php b/resources/views/export/template.blade.php index bd02a21..f9ee366 100644 --- a/resources/views/export/template.blade.php +++ b/resources/views/export/template.blade.php @@ -10,17 +10,17 @@

MesBR.eu

{{ $coupon->coupon_name }}

-

Diffusions du {{ date('d/m/Y', strtotime($startdate)) }} au {{ date('d/m/Y', strtotime($enddate)) }}

+

{{ __('Diffusions du') }} {{ date('d/m/Y', strtotime($startdate)) }} {{ __('au') }} {{ date('d/m/Y', strtotime($enddate)) }}

-

Détail du coupon

+

{{ __('Détail du coupon') }}

- - - - - - + + + + + + @@ -30,17 +30,17 @@ @if($coupon->coupon_mindatediffusion != null) @else - + @endif @if($coupon->coupon_maxdatediffusion != null) @else - + @endif
NomCodeQuantité totaleQté totale diffuséeDate de début de diffusionDate de fin de diffusion{{ __('Nom') }}{{ __('Code') }}{{ __('Quantité totale') }}{{ __('Qté totale diffusée') }}{{ __('Date de début de diffusion') }}{{ __('Date de fin de diffusion') }}
{{ $coupon->coupon_name }}{{ date('d/m/Y', strtotime($coupon->coupon_mindatediffusion)) }}n/c{{ __('n/c') }}{{ date('d/m/Y', strtotime($coupon->coupon_maxdatediffusion)) }}n/c{{ __('n/c') }}
-

Graphique de diffusion sur la période du {{ date('d/m/Y', strtotime($startdate)) }} au {{ date('d/m/Y', strtotime($enddate)) }}

-

Nombre de coupons diffusés : {{ $coupon->couponPrinted }}

+

{{ __('Graphique de diffusion sur la période du') }} {{ date('d/m/Y', strtotime($startdate)) }} {{ __('au') }} {{ date('d/m/Y', strtotime($enddate)) }}

+

{{ __('Nombre de coupons diffusés') }} : {{ $coupon->couponPrinted }}

diff --git a/resources/views/layouts/header.blade.php b/resources/views/layouts/header.blade.php index 44f9e40..d146f40 100644 --- a/resources/views/layouts/header.blade.php +++ b/resources/views/layouts/header.blade.php @@ -3,11 +3,11 @@

Mes BR

- +
Logo Marque @@ -16,15 +16,15 @@
- + diff --git a/resources/views/layouts/menu.blade.php b/resources/views/layouts/menu.blade.php index ebe0389..7b45de5 100644 --- a/resources/views/layouts/menu.blade.php +++ b/resources/views/layouts/menu.blade.php @@ -7,6 +7,7 @@ Campagnes Coupons Contacts + {{ __('Customers') }} @endif @if(Auth::user()->roles == "superadmin") Maintenance diff --git a/resources/views/maintenance/index.blade.php b/resources/views/maintenance/index.blade.php index c9c76b8..1c5d9aa 100644 --- a/resources/views/maintenance/index.blade.php +++ b/resources/views/maintenance/index.blade.php @@ -2,21 +2,24 @@ @section('content') -

Maintenance

+

+ + {{ __('Maintenance') }} +

-
Gestion des utilisateurs
- Ajouter +
{{ __('Gestion des utilisateurs') }}
+ {{ __('Ajouter') }}
@if (isset($users) && count($users) > 0) - - - - + + + + @foreach ($users as $user) @@ -24,8 +27,12 @@ - - @if (Auth::user()->id == $user->id) + + @if (Auth::user()->id === $user->id) @else @@ -41,53 +48,14 @@
NomEmailRôleSite liés{{ __('Nom') }}{{ __('Email') }}{{ __('Rôle') }}{{ __('Site liés') }}
{{ $user->name }} {{ $user->email }} {{ $user->roles }}{!! $user->linkedSites !!} + @foreach ($user->sites as $site) +

{{ $site->site_nom }}

+ @endforeach +
- {!! Form::open(['id' => 'delete-hidden']) !!} - @method('DELETE') - {!! Form::close() !!} +
+ @csrf + @method('DELETE') +
@endif
-
-
Nettoyage de la base
-
-

Nettoyer les murs (datant de plus d'un an)

-
-
-
-
-
-
-
- Nettoyer -
-
-
-

Nettoyer les paniers (qui ont été imprimés et datant de plus de deux mois)

-
-
-
-
-
-
-
- Nettoyer -
-
-
-

Nettoyer les contacts (qui n'ont pas de paniers ni de murs liés et datant de plus d'un mois)

-
-
-
-
-
-
-
- Nettoyer -
-
- -
-
Erreur d'appels :
+
{{ __("Erreur d'appels") }} :
@endsection @@ -113,7 +81,7 @@ data: { labels: labels, datasets: [{ - label: 'Nombres d\'erreurs API', + label: '{{ __("Erreurs") }}',, data: errorCount, backgroundColor: 'rgba(255, 0, 0, 0.2)', borderColor: 'rgba(255,0,0,1)', @@ -132,164 +100,5 @@ } } }); - - - - $.ajaxSetup({ - headers: { - 'X-CSRF-TOKEN': $('#csrf').val() - } - }); - - - $('.clean-app').on('click', '.clean-contacts', function(event) { - event.preventDefault(); - let loader = $(this).parent().find('.loader-mini'); - loader.show(); - $.fancyConfirm({ - title: " Nettoyage", - message: "Êtes-vous sûr de vouloir nettoyer la base des contacts qui n'ont pas de paniers ni de murs liés et datant de plus d'un mois ?", - okButton: 'Supprimer', - noButton: 'Annuler', - callback: function(value) { - if (value) { - $.ajax({ - url: '/displayCleanContacts', - type: 'POST', - data: {}, - dataType: 'JSON', - success: function(response) { - $.fancyConfirm({ - title: " Nettoyage", - message: "Vous allez supprimer " + response.contact_count + - " contacts qui ont plus d'un mois et qui n'ont pas de panier ni de murs en cours, Êtes-vous sûr ?", - okButton: 'Supprimer', - noButton: "Annuler", - callback: function(value) { - if (value) { - $.ajax({ - url: '/cleanContacts', - type: 'POST', - data: {}, - dataType: 'JSON', - success: function(response) { - $.fancyInfo({ - title: " Succès", - message: "Le nettoyage des contacts a bien été effectué.", - okButton: 'Valider', - noButton: "", - callback: function( - value) { - if ( - value) {} else { - loader - .hide(); - } - } - }); - loader.hide(); - }, - error: function(e) { - console.log(e.responseText); - } - }); - } else { - loader.hide(); - } - } - }); - loader.hide(); - }, - error: function(e) { - console.log(e.responseText); - } - }); - } else { - loader.hide(); - } - } - }); - - }) - - $('.clean-app').on('click', '.clean-walls', function(event) { - event.preventDefault(); - let loader = $(this).parent().find('.loader-mini'); - loader.show(); - $.fancyConfirm({ - title: " Nettoyage", - message: "Êtes-vous sûr de vouloir nettoyer la base des mur imprimés ?", - okButton: 'Supprimer', - noButton: 'Annuler', - callback: function(value) { - if (value) { - $.ajax({ - url: '/cleanWalls', - type: 'POST', - data: {}, - dataType: 'JSON', - success: function(response) { - $.fancyInfo({ - title: " Succès", - message: "Le nettoyage des murs a bien été effectué.", - okButton: 'Valider', - noButton: "", - callback: function(value) { - - } - }); - loader.hide(); - }, - error: function(e) { - console.log(e.responseText); - } - }); - } else { - loader.hide(); - } - } - }); - - }) - - $('.clean-app').on('click', '.clean-baskets', function(event) { - event.preventDefault(); - let loader = $(this).parent().find('.loader-mini'); - loader.show(); - $.fancyConfirm({ - title: " Nettoyage", - message: "Êtes-vous sûr de vouloir nettoyer la base des panier imprimés ?", - okButton: 'Supprimer', - noButton: 'Annuler', - callback: function(value) { - if (value) { - $.ajax({ - url: '/cleanBaskets', - type: 'POST', - data: {}, - dataType: 'JSON', - success: function(response) { - $.fancyInfo({ - title: " Succès", - message: "Le nettoyage des paniers a bien été effectué.", - okButton: 'Valider', - noButton: "", - callback: function(value) { - - } - }); - loader.hide(); - }, - error: function(e) { - console.log(e.responseText); - } - }); - } else { - loader.hide(); - } - } - }); - - }) @endsection diff --git a/resources/views/stats/detail.blade.php b/resources/views/stats/detail.blade.php index e84f784..22ad13e 100644 --- a/resources/views/stats/detail.blade.php +++ b/resources/views/stats/detail.blade.php @@ -28,7 +28,7 @@ Type - {{ $campagne->camp_type }} + {{ $campagne->camp_type->label }} Impression limité diff --git a/resources/views/stats/index.blade.php b/resources/views/stats/index.blade.php index 195ce15..828a847 100644 --- a/resources/views/stats/index.blade.php +++ b/resources/views/stats/index.blade.php @@ -19,10 +19,10 @@ @else - @endif + @endif

{{ $campagne->camp_name }} ({{ $campagne->camp_id }})

-

{{ $campagne->camp_type }}

+

{{ $campagne->camp_type->label }}

{{ $campagne->coupons->count() }} Coupon(s) Des coupons de cette campagne ne sont plus imprimables.

{{ date('d/m/Y', strtotime($campagne->camp_createdate)) }}

@if (isset($campagne->coupons) && $campagne->coupons->count()>0)@endif

@@ -64,7 +64,7 @@ @else @endif - @endif + @endif {{--

{{$coupon->remain}}/{{$coupon->quantity}}

--}} @@ -73,11 +73,14 @@ @endif + {{-- LA CA MARCHE --}} + {{-- @dd('erherh') --}} @endforeach + {{-- LA C PT --}} @else
Pas de campagnes pour ce site
@endif - + @endforeach diff --git a/resources/views/user/create.blade.php b/resources/views/user/create.blade.php index 706510c..5b1e8da 100644 --- a/resources/views/user/create.blade.php +++ b/resources/views/user/create.blade.php @@ -1,7 +1,10 @@ @extends('layouts.main') @section('content') -

Nouvel utilisateur

+

+ + {{ __('Nouvel utilisateur') }} +

@if ($errors->any())
@endif
- {!! Form::open(['route' => 'maintenance.user.create-submit', 'enctype' => 'multipart/form-data', 'autocomplete' => 'off']) !!} +
+ @csrf
-
- {!! Form::label('name', 'Nom *') !!} - {!! Form::text('name', null, ['required']) !!} -
-
- {!! Form::label('email', 'Email *') !!} - {!! Form::input('text', 'email', null, ['required']) !!} -
+
+ +
-
-
- {!! Form::label('password', 'Mot de passe *') !!} - {!! Form::password('password', $attributes = ['autocomplete' => 'new-password', 'required' => 'required', 'minlength' => 6]) !!} -
-
- {!! Form::label('roles', 'Rôle') !!} - {!! Form::select('roles', array('superadmin' => 'Super Administrateur', 'admin' => 'Administrateur', 'marketing' => 'Marketing')) !!} -
+
+ +
-

Sites liés

-
- @foreach($sites as $site) -
- - - -
- @endforeach -
-
- Les champs avec un * sont obligatoires - {!! Form::submit('Enregistrer') !!}
- {!! Form::close() !!} +
+
+ + +
+
+ + +
+
+

{{ __('Sites liés') }}

+
+ @foreach($sites as $site) +
+ + + +
+ @endforeach +
+
+ {{ __('Les champs avec un * sont obligatoires') }} + +
+
@endsection diff --git a/resources/views/user/edit.blade.php b/resources/views/user/edit.blade.php index 01ab698..15824cf 100644 --- a/resources/views/user/edit.blade.php +++ b/resources/views/user/edit.blade.php @@ -1,27 +1,44 @@ @extends('layouts.main') @section('content') -

Editer l'utilisateur

+

+ + {{ __("Editer l'utilisateur") }} +

+@if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+@endif
- {!! Form::model($user, ['route' => ['maintenance.user.edit-submit', $user], 'enctype' => 'multipart/form-data']) !!} +
+ @csrf @method('PUT')
- {!! Form::label('name', 'Nom') !!} - {!! Form::text('name', null, ['required']) !!} + +
- {!! Form::label('email', 'Email') !!} - {!! Form::input('text', 'email', null, ['required']) !!} + +
- {!! Form::label('roles', 'Rôle') !!} - {!! Form::select('roles', array('superadmin' => 'Super Administrateur', 'admin' => 'Administrateur', 'marketing' => 'Marketing')) !!} + +
-

Sites liés

+

{{ __('Sites liés') }}

@foreach($sites as $site)
@@ -32,8 +49,8 @@ @endforeach
- {!! Form::submit('Enregistrer') !!} +
- {!! Form::close() !!} +
@endsection diff --git a/routes/api.php b/routes/api.php index 75b24a4..b409639 100644 --- a/routes/api.php +++ b/routes/api.php @@ -14,6 +14,8 @@ Route::prefix('v1') ->group(function () { Route::get('/customer/mur', 'Api\v1\CustomerController@mur'); + Route::get('/customer/basket', 'Api\v1\CustomerController@basket'); + Route::get('/customer/basket/print', 'Api\v1\CustomerController@printBasket'); Route::resource('customer', 'Api\v1\CustomerController'); Route::get('/mur', 'Api\v1\MurController@show'); Route::get('/coupons/print', 'Api\v1\CouponsController@print'); diff --git a/routes/web.php b/routes/web.php index 2e457e3..0d63bf0 100644 --- a/routes/web.php +++ b/routes/web.php @@ -13,10 +13,20 @@ Route::group(['prefix' => 'bo', 'middleware' => ['auth', 'saveroute']], function Route::get('/statistiques/{campagne}', 'StatistiqueController@statdetail')->name('camp.stats'); // ---- Routes pour les ressources en CRUD Route::resource('coupon', 'CouponController')->except(['show'])->middleware('admin'); + // Routes campagne + Route::post('/campagne/{campaign}/add-coupons', 'CampagneController@addCoupons'); + Route::post('/campagne/{campaign}/remove-coupon/{coupon}', 'CampagneController@removeCoupon'); + Route::post('/campagne/{campaign}/new-notification', 'CampagneController@saveNotification')->name('campaign.new-notif'); + Route::delete('/campagne/delete-notification/{notification}', 'CampagneController@deleteNotification')->name('campaign.delete-notif'); + Route::post('/campagne/{campaign}/showDemo', 'CampagneController@showDemo'); + Route::post('/campagne/{campaign}/toggle-publish/{state}', 'CampagneController@togglePublish'); Route::resource('campagne', 'CampagneController')->except(['show'])->middleware('admin'); + + // Routes contact + Route::get('/contact/search', 'ContactController@search')->name('contact.search'); + Route::post('/contact/{contact}/load-widget', 'ContactController@loadWidget'); Route::resource('contact', 'ContactController')->except(['show', 'destroy'])->middleware('admin'); Route::resource('site', 'SiteController')->except(['destroy', 'show'])->middleware('admin'); - Route::resource('mouvement', 'MouvementController')->only(['store'])->middleware('admin'); // ----- Routes de la partie maintenance Route::group(['middleware' => ['superadmin']], function () { Route::get('maintenance', 'MaintenanceController@show')->middleware('superadmin')->name('maintenance.show'); @@ -34,44 +44,23 @@ Route::group(['prefix' => 'bo', 'middleware' => ['auth', 'saveroute']], function // ---- Logout Route::get('logout', '\App\Http\Controllers\Auth\LoginController@logout'); // ---- Route de changement de site - Route::post('/change_marque', 'ChangeMarqueController')->name('change_marque'); - // ---- Routes pour la gestion des mails d'alerte - Route::post('new-notification', array('uses' => 'CampagneController@saveNotification')); - Route::delete('delete-notification/{id}', array('uses' => 'CampagneController@deleteNotification')) - ->name('campagne.deleteNotif'); + Route::post('/brand-change', 'BrandChangeController')->name('brand-change'); // ---- Route Export PDF - Route::get('export', 'ExportController@show')->name('export.coupons'); + Route::get('/export', 'ExportController@show')->name('export.coupons'); + Route::post('/export/coupon', 'ExportController@exportCoupons'); + + // VB : V2 + Route::resource('{site}/customer', 'CustomersController'); }); // ---- Routes Ajax: -// ---- Ajout/Suppression de coupons dans une campagne -Route::post('/add-coupons-ajax', 'AjaxController@add_coupons_ajax'); -Route::post('/remove-coupons-ajax', 'AjaxController@remove_coupons_ajax'); // ---- Publier/Depublier des coupons -Route::post('/unpublish-coupons-ajax', 'AjaxController@unpublish_coupons_ajax'); -Route::post('/publish-coupons-ajax', 'AjaxController@publish_coupons_ajax'); -// ---- Publier/Depublier des campagnes -Route::post('/unpublish-camp-ajax', 'AjaxController@unpublish_camp_ajax'); -Route::post('/publish-camp-ajax', 'AjaxController@publish_camp_ajax'); +Route::post('/unpublish-coupons-ajax', 'AjaxController@unpublishCouponsAjax'); +Route::post('/publish-coupons-ajax', 'AjaxController@publishCouponsAjax'); // ---- Impressions de coupons -Route::post('/ajx_impression', 'AjaxController@impression_ajax'); -// ---- Recherche Contact -Route::any('bo/contact/search', 'ContactController@search_contact')->name('contact.search'); -// ---- Chargement des widget par contacts -Route::post('/loadWidget', 'AjaxController@load_widget'); -// ---- Visualisation des campagnes avec un compte démo -Route::post('/showDemo', 'AjaxController@show_demo'); -// ---- Maintenance : Nettoyer les contacts -Route::post('/displayCleanContacts', 'AjaxController@get_clean_contacts_detail'); -Route::post('/cleanContacts', 'AjaxController@clean_contacts'); -// ---- Maintenance : Nettoyer les murs -Route::post('/cleanWalls', 'AjaxController@clean_walls'); -// ---- Maintenance : Nettoyer les paniers -Route::post('/cleanBaskets', 'AjaxController@clean_baskets'); +Route::post('/ajx_impression', 'AjaxController@impressionAjax'); // ---- Statistiques -Route::post('/loadStat', 'StatistiqueController@load_stats'); -// ---- Export Coupons -Route::post('/exportCoupon', 'ExportController@export_coupons'); +Route::post('/loadStat', 'StatistiqueController@loadStats'); From a853cebf8add44fb655f2c34016f93108a40f163 Mon Sep 17 00:00:00 2001 From: Noah Lancien Date: Thu, 14 Aug 2025 17:05:09 +0200 Subject: [PATCH 2/8] feat: update basket management --- .composer/.htaccess | 1 - .gitignore | 7 + app/Campagne.php | 8 +- app/Enums/LogActions.php | 35 ++++ app/Http/Controllers/AjaxController.php | 27 +++ .../Controllers/Api/v1/BasketController.php | 188 ++++++++++++++++++ .../Controllers/Api/v1/CouponsController.php | 11 +- .../Controllers/Api/v1/CustomerController.php | 22 +- app/Http/Controllers/Api/v1/MurController.php | 2 +- app/Http/Controllers/CampagneController.php | 3 - app/Http/Controllers/ContactController.php | 2 +- app/Http/Controllers/CustomersController.php | 2 +- app/Http/Controllers/PanierController.php | 1 - app/Models/Basket.php | 4 +- app/Models/BasketContent.php | 12 ++ app/Models/Customers.php | 16 +- .../{PrintedWalls.php => PrintedCoupons.php} | 2 +- app/Traits/CouponTrait.php | 6 +- app/Traits/GenerateBR.php | 43 +++- app/Traits/LogTrait.php | 97 +++++++++ clients/MesBR_V1.php | 68 +++++++ ...7_29_000000_rename_printed_walls_table.php | 27 +++ ...nd_deleted_at_in_printed_coupons_table.php | 34 ++++ ...2025_10_22_094302_create_baskets_table.php | 39 ++++ ...22_101537_create_basket_contents_table.php | 39 ++++ resources/views/customers/index.blade.php | 1 - routes/api.php | 1 + routes/web.php | 3 + 28 files changed, 655 insertions(+), 46 deletions(-) delete mode 100644 .composer/.htaccess create mode 100644 app/Enums/LogActions.php create mode 100644 app/Http/Controllers/Api/v1/BasketController.php rename app/Models/{PrintedWalls.php => PrintedCoupons.php} (81%) create mode 100644 app/Traits/LogTrait.php create mode 100644 database/migrations/2025_07_29_000000_rename_printed_walls_table.php create mode 100644 database/migrations/2025_07_29_000001_add_id_and_deleted_at_in_printed_coupons_table.php create mode 100644 database/migrations/2025_10_22_094302_create_baskets_table.php create mode 100644 database/migrations/2025_10_22_101537_create_basket_contents_table.php diff --git a/.composer/.htaccess b/.composer/.htaccess deleted file mode 100644 index 14249c5..0000000 --- a/.composer/.htaccess +++ /dev/null @@ -1 +0,0 @@ -Deny from all \ No newline at end of file diff --git a/.gitignore b/.gitignore index 179bfe8..d9f19a3 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,10 @@ npm-debug.log yarn-error.log .npmrc /tests/coverage + + + + +/.npm +.zshrc +/.composer diff --git a/app/Campagne.php b/app/Campagne.php index 6837a45..e5d84c6 100644 --- a/app/Campagne.php +++ b/app/Campagne.php @@ -4,7 +4,7 @@ namespace App; use App\Enums\CampaignTypes; use App\Models\Basket; -use App\Models\PrintedWalls; +use App\Models\PrintedCoupons; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; @@ -107,12 +107,12 @@ class Campagne extends Model } /** - * printedWalls relation + * printedCoupons relation * @return HasMany */ - public function printedWalls() + public function printedCoupons() { - return $this->hasMany(PrintedWalls::class, 'camp_id'); + return $this->hasMany(PrintedCoupons::class, 'camp_id'); } /** diff --git a/app/Enums/LogActions.php b/app/Enums/LogActions.php new file mode 100644 index 0000000..e2ad939 --- /dev/null +++ b/app/Enums/LogActions.php @@ -0,0 +1,35 @@ + 1, + 'delctrlcontact' => 2, + 'impression' => 3, + 'updatecontact' => 4, + ]; + } + + protected static function labels(): array + { + return [ + 'updatebasket' => 'Mise à jour du panier', + 'delctrlcontact' => 'Réinitialisation du mur', + 'impression' => 'Impression', + 'updatecontact' => 'Mise à jour du contact', + ]; + } +} diff --git a/app/Http/Controllers/AjaxController.php b/app/Http/Controllers/AjaxController.php index d2597d3..3ccd2d4 100644 --- a/app/Http/Controllers/AjaxController.php +++ b/app/Http/Controllers/AjaxController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Campagne; use App\Coupon; use App\Enums\CampaignTypes; use App\PanierContent; @@ -17,6 +18,32 @@ class AjaxController extends Controller use GenerateBR; use CouponTrait; + // ---- Ajoute des coupons à une campagne + public function add_coupons_ajax(Request $request) + { + $camp_id = $request->input('campid'); + $campagne = Campagne::where('camp_id', $camp_id)->first(); + $ids = $request->input('ids'); + + $coupons = Coupon::find($ids); + $campagne->coupons()->attach($coupons); + + return json_encode($coupons); + } + + // ---- Efface des coupons d'une campagne + public function remove_coupons_ajax(Request $request) + { + $camp_id = $request->input('campid'); + $campagne = Campagne::where('camp_id', $camp_id)->first(); + $coupon_id = $request->input('couponid'); + $coupon = Coupon::find($coupon_id); + + $campagne->coupons()->detach($coupon_id); + + return json_encode($coupon); + } + // ---- Dépublie un coupon public function unpublishCouponsAjax(Request $request) { diff --git a/app/Http/Controllers/Api/v1/BasketController.php b/app/Http/Controllers/Api/v1/BasketController.php new file mode 100644 index 0000000..dbd3c74 --- /dev/null +++ b/app/Http/Controllers/Api/v1/BasketController.php @@ -0,0 +1,188 @@ +logAction = LogActions::updatebasket(); + } + + public function updateBasket(Request $request) + { + $this->logSource = isset($request->admin) ? 'ADMIN' : 'API'; + + // ---- Contrôle des paramètres + $error = false; + if ( + (!isset($request->refadherent) || $request->refadherent == "") || + (!isset($request->refcamp) || $request->refcamp == "") || + (!isset($request->refsite) || $request->refsite == "") || + (!isset($request->panier) || $request->panier == "") + ) { + $error = true; + } + + + $refcamp = $request->refcamp; + $campagne = Campagne::where(['camp_id' => $refcamp])->first(); + if (!$campagne || $campagne->camp_published == 0 || ($campagne->camp_type->value != 1 && $campagne->camp_type->value != 3)) { + $this->writeLogErrorIncorrectCampaign($refcamp); + + return response()->json([ + 'Statut' => 'KO', + 'Code' => 103, + 'Message' => 'La campagne n\'existe pas ou n\'est pas un panier' + ]); + } + + $refsite = $request->refsite; + if ($refsite != $campagne->camp_siteId) { + return response()->json([ + 'Statut' => 'KO', + 'Code' => 103, + 'Message' => 'La campagne ne correspond à aucune campagnes du site' + ]); + } + + $panier = $request->panier; + $panierTab = explode(",", $panier); + $coupons = array(); + foreach ($panierTab as $panier) { + $pos = strpos($panier, ")"); + if ($pos === false) { + $error = true; + } + $quantity = substr($panier, 0, $pos); + $quantity = str_replace("(", "", $quantity); + $couponCode = substr($panier, $pos + 1); + + if (is_numeric($quantity) && is_numeric($couponCode) && $quantity > 0) { + if (isset($coupons[$couponCode])) { + $coupons[$couponCode] = $coupons[$couponCode] + $quantity; + } else { + $coupons[$couponCode] = $quantity; + } + } + } + if (count($coupons) == 0) { + die(); + } + + // ---- Préparation message de log avec coupons + if (isset($coupons)) { + $panierCoupons = array(); + foreach ($coupons as $code => $qty) { + array_push($panierCoupons, ['cp_code' => $code, 'qty' => $qty]); + } + } + + if ($error) { + $this->writeLogErrorWrongRequestParameters( + $request->refcamp, + $request->refadherent, + $request->refsite, + $request->panier + ); + + return response()->json([ + 'Statut' => 'KO', + 'Code' => 101, + 'Message' => 'Des paramètres de la requête sont mal renseignés ou non présents' + ]); + } + + // ---- Ajout/MAJ du customer + $customer = Customers::where(['hashcode' => $request->refadherent])->first(); + + // ---- Ajout/MAJ du panier + $panier = Basket::where(['campagne_id' => $refcamp, 'customer_id' => $customer->id])->first(); + if ($panier) { + foreach ($coupons as $couponCode => $quantity) { + $camp = Campagne::where('camp_id', $panier->campagne_id)->first(); + $cp = $camp->coupons()->where('coupon_code', $couponCode)->first(); + if ($cp) { + $panierContent = BasketContent::where(['basket_id' => $panier->id, 'coupon_id' => $cp->coupon_id])->first(); + if ($panierContent) { + $panierContent->quantity = $quantity + $panierContent->quantity; + // MAJ du contenu + $panierContent->save(); + } else { + // Ajout du contenu + $this->addContentToBasket($panier, $cp, $quantity); + } + } + }//end foreach + } else { + // Ajout du panier et du contenu + $panier = new Basket(); + $panier->ref = strtoupper(md5(uniqid(time(), true))); + $panier->campagne()->associate($refcamp); + $panier->customer()->associate($customer); + $panier->save(); + + foreach ($coupons as $couponCode => $quantity) { + $cp = $campagne->coupons()->where('coupon_code', $couponCode)->first(); + if ($cp) { + // Ajout du contenu + $this->addContentToBasket($panier, $cp, $quantity); + } + } + }//end if + + $this->writeLogSuccessBasketCreated( + $request->refcamp, + $request->refadherent, + $request->refsite, + $request->panier, + $panierCoupons + ); + + return response()->json([ + 'Statut' => 'OK', + 'Panier' => $panier + ]); + } + + /** + * Add a content line for a given basket. + * + * @param Basket $basket Related basket. + * @param Coupon $coupon The coupon that will be added to the basket. + * @param integer $quantity The quantity of coupons. + * @return void + */ + private function addContentToBasket( + Basket $basket, + Coupon $coupon, + int $quantity + ): void { + if ($quantity > 0) { + $newBasketContent = new BasketContent(); + $newBasketContent->basket_id = $basket->id; + $newBasketContent->coupon_id = $coupon->coupon_id; + $newBasketContent->quantity = $quantity; + $newBasketContent->save(); + } + } +} diff --git a/app/Http/Controllers/Api/v1/CouponsController.php b/app/Http/Controllers/Api/v1/CouponsController.php index 1b9852f..36e9cf1 100644 --- a/app/Http/Controllers/Api/v1/CouponsController.php +++ b/app/Http/Controllers/Api/v1/CouponsController.php @@ -9,7 +9,7 @@ use App\Http\Controllers\Controller; use App\Http\Requests\Coupons\PrintRequest; use App\Http\Resources\StatusResource; use App\Models\Customers; -use App\Models\PrintedWalls; +use App\Models\PrintedCoupons; use App\Statistique; use App\Traits\CouponTrait; use App\Traits\GenerateBR; @@ -65,14 +65,7 @@ class CouponsController extends Controller $cp = Coupon::where(['coupon_code' => $coupon_code])->first(); if (!$this->isUsed($cp, $campagne, $customer)) { - $coupons[] = $this->printCoupon($cp, $customer, $crmInfos); - - // ----- mémorise l'impression - $mur = new PrintedWalls(); - $mur->camp_id = $campagne->camp_id; - $mur->customer_id = $customer->id; - $mur->coupon_id = $cp->coupon_id; - $mur->save(); + $coupons[] = $this->printCoupon($cp, $customer, $crmInfos, $campagne); // Sauvegarde de stat $stat = new Statistique(); diff --git a/app/Http/Controllers/Api/v1/CustomerController.php b/app/Http/Controllers/Api/v1/CustomerController.php index 56d7b1f..43015e5 100644 --- a/app/Http/Controllers/Api/v1/CustomerController.php +++ b/app/Http/Controllers/Api/v1/CustomerController.php @@ -15,8 +15,10 @@ use App\Http\Requests\Customers\MurRequest; use App\Http\Requests\Customers\PrintBasketRequest; use App\Http\Resources\CouponsResource; use App\Traits\CouponTrait; +use App\Traits\GenerateBR; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Request; +use PDF; use LDC\LDCCRM\CrmApiV2; class CustomerController extends Controller @@ -227,16 +229,18 @@ class CustomerController extends Controller $montantDispo += ($coupon->coupon_value * $content->quantity); $montantTotal += ($coupon->coupon_value * $content->quantity); $couponTotal += $content->quantity; - $coupons = array_merge($coupons, [ + $coupons[] = [ 'quantity' => $content->quantity, + 'printed' => $content->printed, 'coupon' => new CouponsResource($coupon) - ]); + ]; } } } return new StatusResource([ 'status' => "OK", + 'ref' => $basket->ref, 'name' => $campagne->camp_name, 'limited_printing' => $campagne->camp_limitedPrinting, 'available_coupon' => $nbCoupons, @@ -283,12 +287,11 @@ class CustomerController extends Controller if (is_null($crmInfos)) { return new StatusResource(['status' => 'KO', 'code' => 422, 'message' => 'Customer not found.']); } - + $coupons = []; foreach ($basket->contents as $content) { $published = true; $coupon = $content->coupon()->whereRaw("coupon_published = 1 AND (coupon_mindatediffusion IS NULL OR coupon_mindatediffusion <= NOW()) AND (coupon_maxdatediffusion IS NULL OR coupon_maxdatediffusion >= NOW()) AND (coupon_maxdatevalidity IS NULL OR coupon_maxdatevalidity >= NOW())")->first(); - $b64img = $this->printCoupon($coupon, $customer, $crmInfos); if (!is_null($coupon)) { // ---- Contrôle des calendriers de diffusion @@ -302,11 +305,16 @@ class CustomerController extends Controller } } if ($published){ - // TODO : Print coupon - $b64img = $this->printCoupon($coupon, $customer, $crmInfos); - dd($b64img); + for ($i = 0; $i < ($content->quantity - $content->printed); $i++) { + $coupons[] = $this->printCoupon($coupon, $customer, $crmInfos, $campagne); + } + $content->printed = $content->quantity; + $content->save(); } } } + + $pdf = PDF::loadView('pdf.coupons', ['coupons' => $coupons]); + return new StatusResource(['status' => 'OK', 'pdf' => base64_encode($pdf->stream(sprintf("coupons_%s_%s_%s.pdf", $crmInfos->firstname, $crmInfos->lastname, date('d-m-Y'))))]); } } diff --git a/app/Http/Controllers/Api/v1/MurController.php b/app/Http/Controllers/Api/v1/MurController.php index 4b19eec..c6a1d54 100644 --- a/app/Http/Controllers/Api/v1/MurController.php +++ b/app/Http/Controllers/Api/v1/MurController.php @@ -23,7 +23,7 @@ class MurController extends Controller if ($campagne->camp_published === 0) { return new StatusResource(['status' => 'KO', 'code' => 404, 'message' => 'Campaign not found.']); } - if ($campagne->camp_type->equals(CampaignTypes::mur())) { + if (!$campagne->camp_type->equals(CampaignTypes::mur())) { return new StatusResource(['status' => 'KO', 'code' => 403, 'message' => 'Campaign type error.']); } diff --git a/app/Http/Controllers/CampagneController.php b/app/Http/Controllers/CampagneController.php index 9b052c2..949d5a2 100644 --- a/app/Http/Controllers/CampagneController.php +++ b/app/Http/Controllers/CampagneController.php @@ -272,8 +272,6 @@ class CampagneController extends Controller ]); $panier = new \App\Http\Controllers\PanierController(); $res = $panier->updateBasket($req); - - dd($res->original, $req); $url = "/?basketid=" . $res->original['Panier']->Panier; } return json_encode($url); @@ -366,7 +364,6 @@ class CampagneController extends Controller if ($coupons->count() > 0) { foreach ($coupons as $coupon) { - // var_dump($coupon->coupon_id); // ---- Calcul du nombre de BR consommé $diffuseMur = Mur::where(['mur_campId' => $campagne->camp_id, 'mur_couponId' => $coupon->coupon_id])->count(); diff --git a/app/Http/Controllers/ContactController.php b/app/Http/Controllers/ContactController.php index 03bc677..ac15edb 100644 --- a/app/Http/Controllers/ContactController.php +++ b/app/Http/Controllers/ContactController.php @@ -31,7 +31,7 @@ class ContactController extends Controller } - public function search_contact(SearchContact $request) + public function search(SearchContact $request) { session()->put('active_route', 'contact'); diff --git a/app/Http/Controllers/CustomersController.php b/app/Http/Controllers/CustomersController.php index 2811c80..21c1184 100644 --- a/app/Http/Controllers/CustomersController.php +++ b/app/Http/Controllers/CustomersController.php @@ -22,7 +22,7 @@ class CustomersController extends Controller if ($customer) { $publishedCampaign = $site->campagnes->where('camp_published', 1)->pluck('camp_id'); - $campaigns = Campagne::where('camp_published', 1)->whereHas('printedWalls', function ($q) use ($customer, $publishedCampaign) { + $campaigns = Campagne::where('camp_published', 1)->whereHas('printedCoupons', function ($q) use ($customer, $publishedCampaign) { $q->where('customer_id', $customer->id)->whereIn('camp_id', $publishedCampaign); })->get(); } diff --git a/app/Http/Controllers/PanierController.php b/app/Http/Controllers/PanierController.php index de7eead..07fb049 100644 --- a/app/Http/Controllers/PanierController.php +++ b/app/Http/Controllers/PanierController.php @@ -214,7 +214,6 @@ class PanierController extends Controller $panier->save(); $campagne = Campagne::where(['camp_id' => $panier->panier_campId])->first(); - // var_dump($panier->panier_campId); $log->log_refsite = $campagne->camp_siteId; $log->log_contactId = $panier->panier_contactId; $log->log_params = json_encode(['refcamp' => $panier->panier_campId, 'basketid' => $basketid, 'panier' => $panier]); diff --git a/app/Models/Basket.php b/app/Models/Basket.php index 84daa62..5016233 100644 --- a/app/Models/Basket.php +++ b/app/Models/Basket.php @@ -18,7 +18,7 @@ class Basket extends Model */ public function customer() { - return $this->belongsTo(Customers::class); + return $this->belongsTo(Customers::class, 'customer_id', 'id'); } /** @@ -27,7 +27,7 @@ class Basket extends Model */ public function campagne() { - return $this->belongsTo(Campagne::class); + return $this->belongsTo(Campagne::class, 'campagne_id', 'camp_id'); } /** diff --git a/app/Models/BasketContent.php b/app/Models/BasketContent.php index 258f408..a4c912a 100644 --- a/app/Models/BasketContent.php +++ b/app/Models/BasketContent.php @@ -11,6 +11,18 @@ class BasketContent extends Model { use HasFactory; + /** + * booted method + * + * @return void + */ + protected static function booted() + { + static::creating(function (self $model) { + $model->printed = 0; + }); + } + /** * Basket model relation * @return HasOne diff --git a/app/Models/Customers.php b/app/Models/Customers.php index f64ddf6..0d42379 100644 --- a/app/Models/Customers.php +++ b/app/Models/Customers.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Panier; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -62,13 +63,13 @@ class Customers extends Model } /** - * Printed Walls Relation + * Printed Coupons Relation * * @return HasMany */ - public function printedWalls() + public function printedCoupons() { - return $this->hasMany(PrintedWalls::class, 'customer_id', 'id'); + return $this->hasMany(PrintedCoupons::class, 'customer_id', 'id'); } /** @@ -79,4 +80,13 @@ class Customers extends Model { return $this->hasMany(Basket::class, 'customer_id', 'id'); } + + /** + * Basket model relation + * @return HasMany + */ + public function paniers() + { + return $this->hasMany(Panier::class, 'customer_id', 'id'); + } } diff --git a/app/Models/PrintedWalls.php b/app/Models/PrintedCoupons.php similarity index 81% rename from app/Models/PrintedWalls.php rename to app/Models/PrintedCoupons.php index fca2348..1ac08c8 100644 --- a/app/Models/PrintedWalls.php +++ b/app/Models/PrintedCoupons.php @@ -5,7 +5,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; -class PrintedWalls extends Model +class PrintedCoupons extends Model { use HasFactory; } diff --git a/app/Traits/CouponTrait.php b/app/Traits/CouponTrait.php index 68e1c20..dd1a59b 100644 --- a/app/Traits/CouponTrait.php +++ b/app/Traits/CouponTrait.php @@ -8,7 +8,7 @@ use App\Contact; use App\Calendrier; use App\Enums\CampaignTypes; use App\Models\Customers; -use App\Models\PrintedWalls; +use App\Models\PrintedCoupons; use App\Mouvement; use App\Mur; use App\Panier; @@ -342,7 +342,7 @@ trait CouponTrait // ---- Contrôle le nombre limite d'impression if ($coupon->coupon_enablelimitquantity === 1) { - $count = PrintedWalls::where( + $count = PrintedCoupons::where( [ ['camp_id', '=', $campagne->camp_id], ['coupon_id', '=', $coupon->coupon_id], @@ -357,7 +357,7 @@ trait CouponTrait // ---- Contrôle la limite de période de diffusion if ($coupon->coupon_enablelimitperiod === 1 && $coupon->coupon_limitperiod > 0) { - $count = PrintedWalls::where( + $count = PrintedCoupons::where( [ ['camp_id', '=', $campagne->camp_id], ['coupon_id', '=', $coupon->coupon_id], diff --git a/app/Traits/GenerateBR.php b/app/Traits/GenerateBR.php index 45a80e2..d52aec2 100644 --- a/app/Traits/GenerateBR.php +++ b/app/Traits/GenerateBR.php @@ -2,10 +2,14 @@ namespace App\Traits; +use App\Campagne; use App\Models\Customers; use Illuminate\Http\Request; use App\Coupon; +use App\Log; +use App\Models\PrintedCoupons; use App\Site; +use Illuminate\Support\Facades\Log as FacadesLog; trait GenerateBR { @@ -372,7 +376,7 @@ trait GenerateBR } // VB 22/04/2021 - public function printCoupon(Coupon $coupon, Customers $customer, $crmInfos) + public function printCoupon(Coupon $coupon, Customers $customer, $crmInfos, Campagne $campagne) { $arial = public_path() . '/build/assets/fonts/coupons/arial.ttf'; $arialbd = public_path() . '/build/assets/fonts/coupons/arialbd.ttf'; @@ -425,21 +429,29 @@ trait GenerateBR // ---- Inscription de l'utilisateur et de la ville $libelle = "OFFRE RÉSERVÉE À"; $user = sprintf( - "%s %s\r\n%s\r\n%s %s\r\n%s", + "%s\r\n%s\r\n%s\r\n%s %s\r\n%s", $crmInfos->firstname, $crmInfos->lastname, $crmInfos->address, $crmInfos->postcode, $crmInfos->city, - sprintf("id %s", $crmInfos->crm_id) + sprintf('id #%s', str_pad($customer->id, 13, "0", STR_PAD_LEFT)) ); - $ImageFond = imagecreate(120, 40); + $ImageFond = imagecreate(120, 80); $white = imagecolorallocate($ImageFond, 255, 255, 255); $black = imagecolorallocate($ImageFond, 0, 0, 0); - $box1 = imagettfbbox(8, 0, $arial, $user); $box2 = imagettfbbox(8, 0, $arial, $libelle); imagettftext($ImageFond, 8, 0, (120 / 2) - (abs($box2[4]) - abs($box2[6])) / 2, 10, $black, $arialbd, $libelle); - imagettftext($ImageFond, 8, 0, (120 / 2) - (abs($box1[4]) - abs($box1[6])) / 2, 25, $black, $arial, $user); + + // Write line by line. + $userLines = explode("\r\n", $user); + $y = 25; + foreach ($userLines as $line) { + $box = imagettfbbox(8, 0, $arial, $line); + $this->imagettftextSp($ImageFond, 8, 0, (120 / 2) - (abs($box[4]) - abs($box[6])) / 2, $y, $black, $arial, $line, 0.01); + $y = $y + 10; + } + imagecolortransparent($ImageFond, $white); imagecopy($mask, $ImageFond, 770, 40, 0, 0, imagesx($ImageFond), imagesy($ImageFond)); @@ -450,7 +462,7 @@ trait GenerateBR $dateValidite = date('d/m/Y', strtotime("+" . $coupon->coupon_validity . " day")); } else { $dateValidite = date('d/m/Y', strtotime("+30 day")); -// 30 jours par défaut + // 30 jours par défaut } $ImageFond = imagecreate(120, 40); @@ -461,8 +473,11 @@ trait GenerateBR imagecolortransparent($ImageFond, $white); imagecopy($mask, $ImageFond, 780, 142, 0, 0, imagesx($ImageFond), imagesy($ImageFond)); + // Save printed coupon. + $printedCoupon = $this->savePrintedCoupon($campagne, $customer, $coupon); + // Right barcode - $codeBarre = "0000000000001"; // FIXME : put real id here + $codeBarre = str_pad($printedCoupon->id, 13, "0", STR_PAD_LEFT); $generator = new \Picqer\Barcode\BarcodeGeneratorPNG(); $base64 = $generator->getBarcode($codeBarre, $generator::TYPE_CODE_128_A, 2, 46); $barcodeImage = imagecreatefromstring($base64); @@ -539,4 +554,16 @@ trait GenerateBR imagedestroy($ImageFond); return $dataUri; } + + public function savePrintedCoupon(Campagne $campagne, Customers $customer, Coupon $coupon): PrintedCoupons + { + // ----- mémorise l'impression + $mur = new PrintedCoupons(); + $mur->camp_id = $campagne->camp_id; + $mur->customer_id = $customer->id; + $mur->coupon_id = $coupon->coupon_id; + $mur->save(); + + return $mur; + } } diff --git a/app/Traits/LogTrait.php b/app/Traits/LogTrait.php new file mode 100644 index 0000000..2ac4e91 --- /dev/null +++ b/app/Traits/LogTrait.php @@ -0,0 +1,97 @@ +log_action = $this->logAction->value; + $log->log_source = $this->logSource; + $log->log_params = json_encode(['refcamp' => $refcamp]); + $log->log_statut = 'NOK'; + $log->log_message = "La campagne n'existe pas ou n'est pas un panier"; + $log->save(); + } + + /** + * Write error log when there's a wrong or missing parameter in the request. + * + * @param string $refcamp + * @param string $refadherent + * @param string $refsite + * @param string $panier + * @return void + */ + public function writeLogErrorWrongRequestParameters( + string $refcamp, + string $refadherent, + string $refsite, + string $panier + ) { + $log = new Log(); + $log->log_action = $this->logAction->value; + $log->log_source = $this->logSource; + $log->log_params = json_encode([ + 'refcamp' => $refcamp, + 'refadherent' => $refadherent, + 'refsite' => $refsite, + 'panier' => $panier + ]); + $log->log_statut = 'NOK'; + $log->log_message = "Des paramètres de la requête sont mal renseignés ou non présents"; + $log->save(); + } + + /** + * Write success log when the basket is created or updated. + * + * @param string $refcamp + * @param string $refadherent + * @param string $refsite + * @param string $panier + * @param array $panierCoupons + * @return void + */ + public function writeLogSuccessBasketCreated( + string $refcamp, + string $refadherent, + string $refsite, + string $panier, + array $panierCoupons + ) { + $log = new Log(); + $log->log_action = $this->logAction->value; + $log->log_source = $this->logSource; + $log->log_params = json_encode([ + 'refcamp' => $refcamp, + 'refadherent' => $refadherent, + 'refsite' => $refsite, + 'panier' => $panier + ]); + $log->log_refsite = $refsite; + $log->log_statut = 'OK'; + $log->log_message = json_encode( + ['Message' => 'Le panier a bien été créé/mis à jour', 'Coupons' => $panierCoupons], + JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES + ); + $log->save(); + } + + + + + + + + +} diff --git a/clients/MesBR_V1.php b/clients/MesBR_V1.php index 9ba8119..e6c180b 100644 --- a/clients/MesBR_V1.php +++ b/clients/MesBR_V1.php @@ -2,6 +2,8 @@ namespace LDCWEB; +use LDC\LDCCRM\Models\Customer; + class MesBR { @@ -81,4 +83,70 @@ class MesBR curl_close($curl); return json_decode($result); } + + // -------- New methods related to baskets (2025/10) + + /** . + * Update basket + * + * @param \LDC\LDCCRM\Models\Customer $crm_customer + * @param string $ref_site + * @param string $coupon_id + * @param integer $quantity + * @return array + * 'status' => 'OK', + * 'link' => URL, + * 'verif' => URL, + * 'basketId' => 'ID' + */ + public function updateBasket( + Customer $crm_customer, + string $ref_site, + string $coupon_id, + int $quantity = 1 + ): array { + $res = $this->callAPI("GET", "/customer/basket/update", [ + 'refsite' => $ref_site, + 'refcamp' => $this->refCamp, + 'refadherent' => $crm_customer->hashcode, + 'panier' => "({$quantity}){$coupon_id}" + ]); + + 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]; + } + return ['status' => 'OK', 'ref' => $res->Panier->ref]; + } + + /** + * Get customer basket + * + * @param \LDC\LDCCRM\Models\Customer $crm_customer + * @return array + */ + public function getBasket( + Customer $crm_customer, + ): array { + $res = $this->callApi("GET", '/customer/basket', [ + 'refcamp' => $this->refCamp, + 'hashcode' => $crm_customer->hashcode + ]); + + 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->status === 'KO') { + error_log("MESBR - CODE : $res->code - MESSAGE : $res->message"); + return ['status' => 'KO', 'code' => $res->code, 'message' => $res->message]; + } + + return ['status' => 'OK', 'coupons' => $res->coupons]; + } } diff --git a/database/migrations/2025_07_29_000000_rename_printed_walls_table.php b/database/migrations/2025_07_29_000000_rename_printed_walls_table.php new file mode 100644 index 0000000..9cafea2 --- /dev/null +++ b/database/migrations/2025_07_29_000000_rename_printed_walls_table.php @@ -0,0 +1,27 @@ +id()->first(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('site_user', function (Blueprint $table) { + $table->dropForeign('site_user_site_id_foreign'); + $table->dropForeign('site_user_user_id_foreign'); + }); + } +}; diff --git a/database/migrations/2025_10_22_094302_create_baskets_table.php b/database/migrations/2025_10_22_094302_create_baskets_table.php new file mode 100644 index 0000000..7e38298 --- /dev/null +++ b/database/migrations/2025_10_22_094302_create_baskets_table.php @@ -0,0 +1,39 @@ +id(); + $table->string('ref', 36)->comment('Hashcode'); + + $table->integer('campagne_id')->comment('Id de la campagne associée'); + $table->foreign('campagne_id')->references('camp_id')->on('campagne'); + + $table->bigInteger('customer_id')->comment('Id du customer associé'); + $table->foreign('customer_id')->references('id')->on('customers'); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('baskets'); + } +} diff --git a/database/migrations/2025_10_22_101537_create_basket_contents_table.php b/database/migrations/2025_10_22_101537_create_basket_contents_table.php new file mode 100644 index 0000000..e14b556 --- /dev/null +++ b/database/migrations/2025_10_22_101537_create_basket_contents_table.php @@ -0,0 +1,39 @@ +id(); + $table->integer('quantity')->comment('Quantité de ce coupon dans le panier'); + $table->integer('printed')->comment('Quantité de coupons imprimés dans ce panier'); + + $table->foreignId('basket_id')->constrained('baskets'); + + $table->integer('coupon_id')->comment('Id du coupon associé'); + $table->foreign('coupon_id')->references('coupon_id')->on('coupon'); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('basket_contents'); + } +} diff --git a/resources/views/customers/index.blade.php b/resources/views/customers/index.blade.php index 6ce22c4..5f17d09 100644 --- a/resources/views/customers/index.blade.php +++ b/resources/views/customers/index.blade.php @@ -44,7 +44,6 @@ @endif - @dump($campaigns) @foreach ($campaigns as $campaign)

{{ $campaign->camp_name }}

diff --git a/routes/api.php b/routes/api.php index b409639..0c69088 100644 --- a/routes/api.php +++ b/routes/api.php @@ -16,6 +16,7 @@ Route::prefix('v1') Route::get('/customer/mur', 'Api\v1\CustomerController@mur'); Route::get('/customer/basket', 'Api\v1\CustomerController@basket'); Route::get('/customer/basket/print', 'Api\v1\CustomerController@printBasket'); + Route::get('/customer/basket/update', 'Api\v1\BasketController@updateBasket'); Route::resource('customer', 'Api\v1\CustomerController'); Route::get('/mur', 'Api\v1\MurController@show'); Route::get('/coupons/print', 'Api\v1\CouponsController@print'); diff --git a/routes/web.php b/routes/web.php index 0d63bf0..b71c6fa 100644 --- a/routes/web.php +++ b/routes/web.php @@ -54,6 +54,9 @@ Route::group(['prefix' => 'bo', 'middleware' => ['auth', 'saveroute']], function }); // ---- Routes Ajax: +// ---- Ajout/Suppression de coupons dans une campagne +Route::post('/add-coupons-ajax', 'AjaxController@add_coupons_ajax'); +Route::post('/remove-coupons-ajax', 'AjaxController@remove_coupons_ajax'); // ---- Publier/Depublier des coupons Route::post('/unpublish-coupons-ajax', 'AjaxController@unpublishCouponsAjax'); Route::post('/publish-coupons-ajax', 'AjaxController@publishCouponsAjax'); From 1430fc21ae61b774dfc17efad7c5675ab88732d5 Mon Sep 17 00:00:00 2001 From: Noah Lancien Date: Wed, 29 Oct 2025 12:41:39 +0100 Subject: [PATCH 3/8] fix: review feedback --- app/Http/Controllers/Api/v1/BasketController.php | 15 ++++++++------- app/Models/Customers.php | 4 ++-- clients/MesBR_V1.php | 15 +++++++-------- ...id_and_deleted_at_in_printed_coupons_table.php | 6 +++--- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/app/Http/Controllers/Api/v1/BasketController.php b/app/Http/Controllers/Api/v1/BasketController.php index dbd3c74..92a2006 100644 --- a/app/Http/Controllers/Api/v1/BasketController.php +++ b/app/Http/Controllers/Api/v1/BasketController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Api\v1; use Illuminate\Http\Request; +use Illuminate\Support\Str; use App\Campagne; use App\Coupon; use App\Enums\LogActions; @@ -35,7 +36,7 @@ class BasketController extends Controller // ---- Contrôle des paramètres $error = false; if ( - (!isset($request->refadherent) || $request->refadherent == "") || + (!isset($request->customer_hashcode) || $request->customer_hashcode == "") || (!isset($request->refcamp) || $request->refcamp == "") || (!isset($request->refsite) || $request->refsite == "") || (!isset($request->panier) || $request->panier == "") @@ -100,8 +101,8 @@ class BasketController extends Controller if ($error) { $this->writeLogErrorWrongRequestParameters( $request->refcamp, - $request->refadherent, - $request->refsite, + $request->customer_hashcode, + $campagne->camp_siteId, $request->panier ); @@ -112,8 +113,8 @@ class BasketController extends Controller ]); } - // ---- Ajout/MAJ du customer - $customer = Customers::where(['hashcode' => $request->refadherent])->first(); + // ---- Récupération du Customer + $customer = Customers::where(['hashcode' => $request->customer_hashcode])->first(); // ---- Ajout/MAJ du panier $panier = Basket::where(['campagne_id' => $refcamp, 'customer_id' => $customer->id])->first(); @@ -136,7 +137,7 @@ class BasketController extends Controller } else { // Ajout du panier et du contenu $panier = new Basket(); - $panier->ref = strtoupper(md5(uniqid(time(), true))); + $panier->ref = Str::uuid(); $panier->campagne()->associate($refcamp); $panier->customer()->associate($customer); $panier->save(); @@ -152,7 +153,7 @@ class BasketController extends Controller $this->writeLogSuccessBasketCreated( $request->refcamp, - $request->refadherent, + $request->customer_hashcode, $request->refsite, $request->panier, $panierCoupons diff --git a/app/Models/Customers.php b/app/Models/Customers.php index 0d42379..55e03ef 100644 --- a/app/Models/Customers.php +++ b/app/Models/Customers.php @@ -82,11 +82,11 @@ class Customers extends Model } /** - * Basket model relation + * Panier model relation * @return HasMany */ public function paniers() { - return $this->hasMany(Panier::class, 'customer_id', 'id'); + return $this->hasMany(Panier::class, 'panier_contactId', 'id'); } } diff --git a/clients/MesBR_V1.php b/clients/MesBR_V1.php index e6c180b..61cd947 100644 --- a/clients/MesBR_V1.php +++ b/clients/MesBR_V1.php @@ -11,10 +11,13 @@ class MesBR private $refCamp; - public function __construct($endpoint, $refCamp) + private $refSite; + + public function __construct($endpoint, $refCamp, $refSite) { $this->endpoint = $endpoint; $this->refCamp = $refCamp; + $this->refSite = $refSite; } public function createCustomer(string $hashcode, string $email, string $country) @@ -90,25 +93,21 @@ class MesBR * Update basket * * @param \LDC\LDCCRM\Models\Customer $crm_customer - * @param string $ref_site * @param string $coupon_id * @param integer $quantity * @return array * 'status' => 'OK', - * 'link' => URL, - * 'verif' => URL, - * 'basketId' => 'ID' + * 'ref' => Basket REF, */ public function updateBasket( Customer $crm_customer, - string $ref_site, string $coupon_id, int $quantity = 1 ): array { $res = $this->callAPI("GET", "/customer/basket/update", [ - 'refsite' => $ref_site, + 'refsite' => $this->refSite, 'refcamp' => $this->refCamp, - 'refadherent' => $crm_customer->hashcode, + 'customer_hashcode' => $crm_customer->hashcode, 'panier' => "({$quantity}){$coupon_id}" ]); diff --git a/database/migrations/2025_07_29_000001_add_id_and_deleted_at_in_printed_coupons_table.php b/database/migrations/2025_07_29_000001_add_id_and_deleted_at_in_printed_coupons_table.php index 6d4b7b0..ad0efb1 100644 --- a/database/migrations/2025_07_29_000001_add_id_and_deleted_at_in_printed_coupons_table.php +++ b/database/migrations/2025_07_29_000001_add_id_and_deleted_at_in_printed_coupons_table.php @@ -26,9 +26,9 @@ return new class extends Migration */ public function down() { - Schema::table('site_user', function (Blueprint $table) { - $table->dropForeign('site_user_site_id_foreign'); - $table->dropForeign('site_user_user_id_foreign'); + Schema::table('printed_coupons', function (Blueprint $table) { + $table->dropColumn('id'); + $table->dropSoftDeletes(); }); } }; From 0d5fbfccb62fe92626b056cecab7a8fdf5e34213 Mon Sep 17 00:00:00 2001 From: Vincent BRIDIER Date: Wed, 5 Nov 2025 21:09:05 +0100 Subject: [PATCH 4/8] feat: Somes f***in changes, vivement qu'on sorte le legacy --- .env.example | 6 +- app/Console/Commands/CustomersClaims.php | 89 +++++++ app/Console/Kernel.php | 16 +- .../Controllers/Api/v1/BasketController.php | 170 +++++++------ .../Controllers/Api/v1/CouponsController.php | 2 +- .../Controllers/Api/v1/CustomerController.php | 72 +----- app/Http/Controllers/Fo/BasketController.php | 85 +++++++ .../Controllers/StatistiqueController.php | 33 +-- .../Requests/Customers/PrintBasketRequest.php | 52 ---- app/Http/Requests/Fo/Basket/PrintRequest.php | 49 ++++ app/Traits/GenerateBR.php | 227 +++++++++--------- config/crm.php | 5 - config/services.php | 4 + ...03126_alter_ref_column_on_basket_table.php | 34 +++ package.json | 3 - resources/views/fo/basket/show.blade.php | 49 ++++ resources/views/pdf/coupons.blade.php | 5 +- routes/api.php | 1 - routes/channels.php | 16 -- routes/console.php | 18 -- routes/web.php | 12 + 21 files changed, 554 insertions(+), 394 deletions(-) create mode 100644 app/Console/Commands/CustomersClaims.php create mode 100644 app/Http/Controllers/Fo/BasketController.php delete mode 100644 app/Http/Requests/Customers/PrintBasketRequest.php create mode 100644 app/Http/Requests/Fo/Basket/PrintRequest.php delete mode 100644 config/crm.php create mode 100644 database/migrations/2025_10_30_103126_alter_ref_column_on_basket_table.php create mode 100644 resources/views/fo/basket/show.blade.php delete mode 100644 routes/channels.php delete mode 100644 routes/console.php diff --git a/.env.example b/.env.example index 61908c1..f86eaea 100644 --- a/.env.example +++ b/.env.example @@ -7,7 +7,7 @@ APP_URL=http://localhost LOG_CHANNEL=stack DB_CONNECTION=mysql -DB_HOST=127.0.0.1 +DB_HOST=dev-mysql DB_PORT=3306 DB_DATABASE=homestead DB_USERNAME=homestead @@ -20,8 +20,8 @@ SESSION_DRIVER=file SESSION_LIFETIME=120 MAIL_DRIVER=smtp -MAIL_HOST=smtp.mailtrap.io -MAIL_PORT=2525 +MAIL_HOST=dev-mailpit +MAIL_PORT=1025 MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null diff --git a/app/Console/Commands/CustomersClaims.php b/app/Console/Commands/CustomersClaims.php new file mode 100644 index 0000000..dbf4fb9 --- /dev/null +++ b/app/Console/Commands/CustomersClaims.php @@ -0,0 +1,89 @@ + '01/10/2025', + 'delphine.byon@hotmail.fr' => '30/09/2025', + 'aurel_del@hotmail.fr' => '27/09/2025', + 'katy.lancelot@orange.fr' => '26/09/2025' + ]; + + $emails = array_keys($claims); + + // On récupère les contact dont l'identifiant est un ID de CRM + $contacts = Contact::whereIn('contact_email', $emails) + ->where('contact_siteId', '=', $siteId) + ->get(); + + $toAdd = []; + + // Pour chaque contact + foreach ($contacts as $contact) { + + $claimDate = Carbon::createFromFormat('d/m/Y', $claims[$contact->contact_email])->format('Y-m-d'); + + // On vient chercher les paniers dont la date de création est >= à la date de la récla + $paniers = $contact->paniers()->where('panier_createdate', '>=', $claimDate)->get(); + + foreach ($paniers as $panier) { + foreach ($panier->panierContents as $content) { + $contactEmail = $contact->contact_email; + $campId = $panier->panier_campId; + $couponId = $content->panier_content_couponId; + + if (!isset($toAdd[$contactEmail][$campId])) { + $toAdd[$contactEmail][$campId] = []; + } + if (!isset($toAdd[$contactEmail][$campId][$couponId])) { + $toAdd[$contactEmail][$campId][$couponId] = 0; + } + + $toAdd[$contactEmail][$campId][$couponId] += $content->panier_content_printed; + } + } + } + + dd($toAdd); + + return 0; + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index ef94244..206dd09 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -23,7 +23,9 @@ class Kernel extends ConsoleKernel * * @var array */ - protected $commands = []; + protected $commands = [ + \App\Console\Commands\CustomersClaims::class, + ]; /** * Define the application's command schedule. @@ -104,16 +106,4 @@ class Kernel extends ConsoleKernel } })->weekdays()->at('7:00'); } - - /** - * Register the commands for the application. - * - * @return void - */ - protected function commands() - { - $this->load(__DIR__ . '/Commands'); - - require base_path('routes/console.php'); - } } diff --git a/app/Http/Controllers/Api/v1/BasketController.php b/app/Http/Controllers/Api/v1/BasketController.php index 92a2006..c7c0da0 100644 --- a/app/Http/Controllers/Api/v1/BasketController.php +++ b/app/Http/Controllers/Api/v1/BasketController.php @@ -17,9 +17,7 @@ use App\Traits\LogTrait; class BasketController extends Controller { - use ThanksTrait; - use CouponTrait; - use LogTrait; + use ThanksTrait, CouponTrait, LogTrait; protected $logSource; protected $logAction; @@ -27,157 +25,157 @@ class BasketController extends Controller public function __construct() { $this->logAction = LogActions::updatebasket(); + $this->logSource = 'API'; } public function updateBasket(Request $request) { - $this->logSource = isset($request->admin) ? 'ADMIN' : 'API'; - - // ---- Contrôle des paramètres + // Validate required parameters + $requiredParams = ['customer_hashcode', 'refcamp', 'refsite', 'panier']; $error = false; - if ( - (!isset($request->customer_hashcode) || $request->customer_hashcode == "") || - (!isset($request->refcamp) || $request->refcamp == "") || - (!isset($request->refsite) || $request->refsite == "") || - (!isset($request->panier) || $request->panier == "") - ) { - $error = true; + foreach ($requiredParams as $param) { + if (empty($request->{$param})) { + $error = true; + break; + } } - - $refcamp = $request->refcamp; - $campagne = Campagne::where(['camp_id' => $refcamp])->first(); - if (!$campagne || $campagne->camp_published == 0 || ($campagne->camp_type->value != 1 && $campagne->camp_type->value != 3)) { - $this->writeLogErrorIncorrectCampaign($refcamp); + if ($error) { + $this->writeLogErrorWrongRequestParameters( + $request->refcamp ?? null, + $request->customer_hashcode ?? null, + $request->refsite ?? null, + $request->panier ?? null + ); return response()->json([ - 'Statut' => 'KO', - 'Code' => 103, - 'Message' => 'La campagne n\'existe pas ou n\'est pas un panier' + 'Statut' => 'KO', + 'Code' => 101, + 'Message' => 'Des paramètres de la requête sont mal renseignés ou non présents' + ]); + } + + // Validate campaign + $refcamp = $request->refcamp; + $campagne = Campagne::where('camp_id', $refcamp)->first(); + + if (!$campagne || $campagne->camp_published == 0 || !in_array($campagne->camp_type->value, [1, 3])) { + $this->writeLogErrorIncorrectCampaign($refcamp); + return response()->json([ + 'Statut' => 'KO', + 'Code' => 103, + 'Message' => 'La campagne n\'existe pas ou n\'est pas un panier' ]); } $refsite = $request->refsite; if ($refsite != $campagne->camp_siteId) { return response()->json([ - 'Statut' => 'KO', - 'Code' => 103, - 'Message' => 'La campagne ne correspond à aucune campagnes du site' + 'Statut' => 'KO', + 'Code' => 103, + 'Message' => 'La campagne ne correspond à aucune campagnes du site' ]); } - $panier = $request->panier; - $panierTab = explode(",", $panier); - $coupons = array(); - foreach ($panierTab as $panier) { - $pos = strpos($panier, ")"); + // Parse panier + $panierTab = explode(",", $request->panier); + $coupons = []; + foreach ($panierTab as $item) { + $pos = strpos($item, ")"); if ($pos === false) { $error = true; + break; } - $quantity = substr($panier, 0, $pos); - $quantity = str_replace("(", "", $quantity); - $couponCode = substr($panier, $pos + 1); + + $quantity = str_replace("(", "", substr($item, 0, $pos)); + $couponCode = substr($item, $pos + 1); if (is_numeric($quantity) && is_numeric($couponCode) && $quantity > 0) { if (isset($coupons[$couponCode])) { - $coupons[$couponCode] = $coupons[$couponCode] + $quantity; + $coupons[$couponCode] += $quantity; } else { $coupons[$couponCode] = $quantity; } } } - if (count($coupons) == 0) { - die(); - } - // ---- Préparation message de log avec coupons - if (isset($coupons)) { - $panierCoupons = array(); - foreach ($coupons as $code => $qty) { - array_push($panierCoupons, ['cp_code' => $code, 'qty' => $qty]); - } + if (empty($coupons)) { + die(); } if ($error) { $this->writeLogErrorWrongRequestParameters( - $request->refcamp, + $refcamp, $request->customer_hashcode, $campagne->camp_siteId, $request->panier ); return response()->json([ - 'Statut' => 'KO', - 'Code' => 101, - 'Message' => 'Des paramètres de la requête sont mal renseignés ou non présents' + 'Statut' => 'KO', + 'Code' => 101, + 'Message' => 'Des paramètres de la requête sont mal renseignés ou non présents' ]); } - // ---- Récupération du Customer - $customer = Customers::where(['hashcode' => $request->customer_hashcode])->first(); + // Prepare panierCoupons for logging + $panierCoupons = []; + foreach ($coupons as $code => $qty) { + $panierCoupons[] = ['cp_code' => $code, 'qty' => $qty]; + } - // ---- Ajout/MAJ du panier - $panier = Basket::where(['campagne_id' => $refcamp, 'customer_id' => $customer->id])->first(); - if ($panier) { + // Retrieve customer + $customer = Customers::where('hashcode', $request->customer_hashcode)->first(); + + // Retrieve or create basket + $basket = Basket::where(['campagne_id' => $refcamp, 'customer_id' => $customer->id])->first(); + + if ($basket) { + $camp = Campagne::where('camp_id', $basket->campagne_id)->first(); foreach ($coupons as $couponCode => $quantity) { - $camp = Campagne::where('camp_id', $panier->campagne_id)->first(); - $cp = $camp->coupons()->where('coupon_code', $couponCode)->first(); + $cp = $camp->coupons()->where('coupon_code', $couponCode)->first(); if ($cp) { - $panierContent = BasketContent::where(['basket_id' => $panier->id, 'coupon_id' => $cp->coupon_id])->first(); - if ($panierContent) { - $panierContent->quantity = $quantity + $panierContent->quantity; - // MAJ du contenu - $panierContent->save(); + $basketContent = BasketContent::where(['basket_id' => $basket->id, 'coupon_id' => $cp->coupon_id])->first(); + if ($basketContent) { + $basketContent->quantity += $quantity; + $basketContent->save(); } else { - // Ajout du contenu - $this->addContentToBasket($panier, $cp, $quantity); + $this->addContentToBasket($basket, $cp, $quantity); } } - }//end foreach + } } else { - // Ajout du panier et du contenu - $panier = new Basket(); - $panier->ref = Str::uuid(); - $panier->campagne()->associate($refcamp); - $panier->customer()->associate($customer); - $panier->save(); + $basket = new Basket(); + $basket->ref = (string) Str::uuid(); + $basket->campagne()->associate($refcamp); + $basket->customer()->associate($customer); + $basket->save(); foreach ($coupons as $couponCode => $quantity) { $cp = $campagne->coupons()->where('coupon_code', $couponCode)->first(); if ($cp) { - // Ajout du contenu - $this->addContentToBasket($panier, $cp, $quantity); + $this->addContentToBasket($basket, $cp, $quantity); } } - }//end if + } + // Log success $this->writeLogSuccessBasketCreated( - $request->refcamp, + $refcamp, $request->customer_hashcode, - $request->refsite, + $refsite, $request->panier, $panierCoupons ); return response()->json([ 'Statut' => 'OK', - 'Panier' => $panier + 'Panier' => $basket ]); } - /** - * Add a content line for a given basket. - * - * @param Basket $basket Related basket. - * @param Coupon $coupon The coupon that will be added to the basket. - * @param integer $quantity The quantity of coupons. - * @return void - */ - private function addContentToBasket( - Basket $basket, - Coupon $coupon, - int $quantity - ): void { + private function addContentToBasket(Basket $basket, Coupon $coupon, int $quantity): void + { if ($quantity > 0) { $newBasketContent = new BasketContent(); $newBasketContent->basket_id = $basket->id; diff --git a/app/Http/Controllers/Api/v1/CouponsController.php b/app/Http/Controllers/Api/v1/CouponsController.php index 36e9cf1..7532d36 100644 --- a/app/Http/Controllers/Api/v1/CouponsController.php +++ b/app/Http/Controllers/Api/v1/CouponsController.php @@ -40,7 +40,7 @@ class CouponsController extends Controller return new StatusResource(['status' => 'KO', 'code' => 422, 'message' => 'Coupons not found.']); } - $crm = new CrmApiV2(config('crm.CRM_ENDPOINT'), $campagne->site->crm_token, $campagne->site->crm_password); + $crm = new CrmApiV2(config('services.crm.endpoint'), $campagne->site->crm_token, $campagne->site->crm_password); try { /** diff --git a/app/Http/Controllers/Api/v1/CustomerController.php b/app/Http/Controllers/Api/v1/CustomerController.php index 43015e5..4dcedaf 100644 --- a/app/Http/Controllers/Api/v1/CustomerController.php +++ b/app/Http/Controllers/Api/v1/CustomerController.php @@ -12,14 +12,11 @@ use App\Models\Customers; use App\Http\Controllers\Controller; use App\Http\Requests\Customers\BasketRequest; use App\Http\Requests\Customers\MurRequest; -use App\Http\Requests\Customers\PrintBasketRequest; use App\Http\Resources\CouponsResource; use App\Traits\CouponTrait; use App\Traits\GenerateBR; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Request; -use PDF; -use LDC\LDCCRM\CrmApiV2; class CustomerController extends Controller { @@ -240,7 +237,7 @@ class CustomerController extends Controller return new StatusResource([ 'status' => "OK", - 'ref' => $basket->ref, + 'ref' => $basket->uuid, 'name' => $campagne->camp_name, 'limited_printing' => $campagne->camp_limitedPrinting, 'available_coupon' => $nbCoupons, @@ -250,71 +247,4 @@ class CustomerController extends Controller 'coupons' => $coupons ]); } - - public function printBasket(PrintBasketRequest $request) - { - $datas = $request->validated(); - - $customer = Customers::where('hashcode', $datas['hashcode'])->first(); - $campagne = Campagne::find($datas['refcamp']); - - if (!$campagne->camp_type->equals(CampaignTypes::panier())) { - return new StatusResource(['status' => 'KO', 'code' => 404, 'message' => 'Campaign is not basket.']); - } - if ($campagne->camp_published === 0) { - return new StatusResource(['status' => 'KO', 'code' => 404, 'message' => 'Campaign not found.']); - } - if ($campagne->site->crm_token === null) { - return new StatusResource(['status' => 'KO', 'code' => 500, 'message' => 'CRM Token is missing for site_id : ' . $campagne->site->site_id]); - } - if ($campagne->site->crm_password === null) { - return new StatusResource(['status' => 'KO', 'code' => 500, 'message' => 'CRM Password is missing for site_id : ' . $campagne->site->site_id]); - } - $basket = $customer->baskets()->where('campagne_id', $campagne->camp_id)->first(); - if (is_null($basket)) { - return new StatusResource(['status' => 'KO', 'code' => 404, 'message' => 'Basket not found.']); - } - $crm = new CrmApiV2(config('crm.CRM_ENDPOINT'), $campagne->site->crm_token, $campagne->site->crm_password); - try { - /** - * @var \LDC\LDCCRM\Models\Customer - */ - $crmInfos = $crm->getCustomer($customer->hashcode); - } catch (\Exception $e) { - Log::critical($e->getMessage()); - return new StatusResource(['status' => 'KO', 'code' => 422, 'message' => 'Customer not found.']); - } - if (is_null($crmInfos)) { - return new StatusResource(['status' => 'KO', 'code' => 422, 'message' => 'Customer not found.']); - } - $coupons = []; - foreach ($basket->contents as $content) - { - $published = true; - $coupon = $content->coupon()->whereRaw("coupon_published = 1 AND (coupon_mindatediffusion IS NULL OR coupon_mindatediffusion <= NOW()) AND (coupon_maxdatediffusion IS NULL OR coupon_maxdatediffusion >= NOW()) AND (coupon_maxdatevalidity IS NULL OR coupon_maxdatevalidity >= NOW())")->first(); - if (!is_null($coupon)) - { - // ---- Contrôle des calendriers de diffusion - $calendars = Calendrier::where(['calendrier_couponId' => $coupon->coupon_id])->get(); - if (!$calendars->isEmpty()) { - $published = false; - foreach ($calendars as $calendar) { - if (strtotime(date('Y-m-d H:i:s')) > strtotime($calendar->calendrier_startdate) && strtotime(date('Y-m-d H:i:s')) < strtotime($calendar->calendrier_enddate)) { - $published = true; - } - } - } - if ($published){ - for ($i = 0; $i < ($content->quantity - $content->printed); $i++) { - $coupons[] = $this->printCoupon($coupon, $customer, $crmInfos, $campagne); - } - $content->printed = $content->quantity; - $content->save(); - } - } - } - - $pdf = PDF::loadView('pdf.coupons', ['coupons' => $coupons]); - return new StatusResource(['status' => 'OK', 'pdf' => base64_encode($pdf->stream(sprintf("coupons_%s_%s_%s.pdf", $crmInfos->firstname, $crmInfos->lastname, date('d-m-Y'))))]); - } } diff --git a/app/Http/Controllers/Fo/BasketController.php b/app/Http/Controllers/Fo/BasketController.php new file mode 100644 index 0000000..7669fce --- /dev/null +++ b/app/Http/Controllers/Fo/BasketController.php @@ -0,0 +1,85 @@ +contents->sum(function ($content) { + return $content->quantity - $content->printed; + }); + + return view('fo.basket.show', compact('basket', 'totalQuantity')); + } + + // FIXME: Docblock + public function print(PrintRequest $request, Basket $basket) + { + $campagne = $basket->campagne; + + // FIXME: Bump to CrmApiV4 + $crm = new CrmApiV2(config('services.crm.endpoint'), $campagne->site->crm_token, $campagne->site->crm_password); + + /** + * @var \LDC\LDCCRM\Models\Customer + */ + $crmInfos = $crm->getCustomer($basket->customer->hashcode); + + if (is_null($crmInfos)) { + throw new \Exception('Customer not found.'); + } + + $coupons = []; + $currentDateTime = date('Y-m-d H:i:s'); + + foreach ($basket->contents as $content) { + $coupon = $content->coupon()->whereRaw( + "coupon_published = 1 AND + (coupon_mindatediffusion IS NULL OR coupon_mindatediffusion <= NOW()) AND + (coupon_maxdatediffusion IS NULL OR coupon_maxdatediffusion >= NOW()) AND + (coupon_maxdatevalidity IS NULL OR coupon_maxdatevalidity >= NOW())" + )->first(); + + if ($coupon !== null) { + $calendars = Calendrier::where('calendrier_couponId', $coupon->coupon_id)->get(); + + $published = true; + if (!$calendars->isEmpty()) { + $published = false; + foreach ($calendars as $calendar) { + if ($currentDateTime > $calendar->calendrier_startdate && $currentDateTime < $calendar->calendrier_enddate) { + $published = true; + break; // Exit loop early as condition is met + } + } + } + + if ($published) { + $qtyToPrint = $content->quantity - $content->printed; + for ($i = 0; $i < $qtyToPrint; $i++) { + $coupons[] = $this->printCoupon($coupon, $basket->customer, $crmInfos, $campagne); + } + $content->printed = $content->quantity; + $content->save(); + } + } + } + + // $coupons contains all printed coupons + $pdf = PDF::loadView('pdf.coupons', ['coupons' => $coupons]); + return $pdf->download(sprintf("coupons_%s_%s_%s.pdf", $crmInfos->firstname, $crmInfos->lastname, date('d-m-Y'))); + } +} diff --git a/app/Http/Controllers/StatistiqueController.php b/app/Http/Controllers/StatistiqueController.php index a2e9f59..3227ff2 100644 --- a/app/Http/Controllers/StatistiqueController.php +++ b/app/Http/Controllers/StatistiqueController.php @@ -20,22 +20,23 @@ class StatistiqueController extends Controller */ public function index() { - $sites = Auth::user()->sites()->get(); - foreach ($sites as $site) { - if (isset($site->campagnes)) { - $campagnes = $site->campagnes; - foreach ($campagnes as $campagne) { - $coupons = $campagne->coupons; - foreach ($coupons as $coupon) { - $coupon->quantity = $this->getFullQuantity($coupon); - $coupon->remain = $this->getFullQuantity($coupon) - $this->getUsedQuantity($coupon); - $coupon->diffused = $this->getDiffusedQuantity($coupon); - $coupon->isPrintable = $this->isAvailableToPrint($coupon, $campagne); - $coupon->isPaused = $this->isPausedToPrint($coupon, $campagne); - } - }//end foreach - }//end if - }//end foreach + // $sites = Auth::user()->sites()->get(); + // foreach ($sites as $site) { + // if (isset($site->campagnes)) { + // $campagnes = $site->campagnes; + // foreach ($campagnes as $campagne) { + // $coupons = $campagne->coupons; + // foreach ($coupons as $coupon) { + // $coupon->quantity = $this->getFullQuantity($coupon); + // $coupon->remain = $this->getFullQuantity($coupon) - $this->getUsedQuantity($coupon); + // $coupon->diffused = $this->getDiffusedQuantity($coupon); + // $coupon->isPrintable = $this->isAvailableToPrint($coupon, $campagne); + // $coupon->isPaused = $this->isPausedToPrint($coupon, $campagne); + // } + // }//end foreach + // }//end if + // }//end foreach + $sites = []; return view('stats.index')->with(['sites' => $sites]); } diff --git a/app/Http/Requests/Customers/PrintBasketRequest.php b/app/Http/Requests/Customers/PrintBasketRequest.php deleted file mode 100644 index d9db769..0000000 --- a/app/Http/Requests/Customers/PrintBasketRequest.php +++ /dev/null @@ -1,52 +0,0 @@ - 'string|exists:campagne,camp_id|required', - 'hashcode' => 'string|exists:customers,hashcode|required' - ]; - } - - /** - * failedValidation - * @param Validator $validator - * @return void - * @throws BindingResolutionException - * @throws HttpResponseException - */ - protected function failedValidation(Validator $validator) - { - if ($this->wantsJson()) { - throw new HttpResponseException(response()->json(new StatusResource([ - 'status' => 'KO', - 'code' => 422, - 'message' => $validator->errors()->messages() - ]), 422)); - } - } -} diff --git a/app/Http/Requests/Fo/Basket/PrintRequest.php b/app/Http/Requests/Fo/Basket/PrintRequest.php new file mode 100644 index 0000000..1fb855a --- /dev/null +++ b/app/Http/Requests/Fo/Basket/PrintRequest.php @@ -0,0 +1,49 @@ +basket->campagne; + + if (!$campagne->camp_type->equals(CampaignTypes::panier())) { + throw new \Exception('Campaign is not basket.'); + } + + if ($campagne->camp_published === 0) { + throw new \Exception('Campaign is not published.'); + } + if ($campagne->site->crm_token === null) { + throw new \Exception('CRM Token is missing.'); + } + if ($campagne->site->crm_password === null) { + throw new \Exception('CRM Password is missing.'); + } + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return []; + } +} diff --git a/app/Traits/GenerateBR.php b/app/Traits/GenerateBR.php index d52aec2..20e5113 100644 --- a/app/Traits/GenerateBR.php +++ b/app/Traits/GenerateBR.php @@ -6,10 +6,8 @@ use App\Campagne; use App\Models\Customers; use Illuminate\Http\Request; use App\Coupon; -use App\Log; use App\Models\PrintedCoupons; use App\Site; -use Illuminate\Support\Facades\Log as FacadesLog; trait GenerateBR { @@ -42,7 +40,10 @@ trait GenerateBR $white = imagecolorallocate($ImageMentionFond, 255, 255, 255); imagefilledrectangle($ImageMentionFond, 0, 0, 414, 99, $white); $black = imagecolorallocate($ImageMentionFond, 0, 0, 0); - $lines_mentions = explode("\r\n", $txt_mentions); + + $cleanedString = str_replace(["\r", "\n"], '', $txt_mentions); + $lines_mentions = str_split($cleanedString, 92); + $y = 19; foreach ($lines_mentions as $line) { $this->imagettftextSp($ImageMentionFond, 7, 0, 15, $y, $black, $arial, $line, 0.01); @@ -147,7 +148,7 @@ trait GenerateBR } imagecolortransparent($ImageFond, $white); imagecopy($mask, $ImageFond, 360, 170, 0, 0, imagesx($ImageFond), imagesy($ImageFond)); - }//end if + } //end if // ---- Copie le produit dans le mask $size = getimagesize($product_image); @@ -276,8 +277,8 @@ trait GenerateBR } // imagecolortransparent($ImageMentionFond, $white); imagecopy($mask, $ImageMentionFond, 335, 235, 0, 0, imagesx($ImageMentionFond), imagesy($ImageMentionFond)); - }//end if - }//end if + } //end if + } //end if // ---- Inscription de l'utilisateur et de la ville @@ -300,7 +301,7 @@ trait GenerateBR $dateValidite = date('d/m/Y', strtotime("+" . $coupon->coupon_validity . " day")); } else { $dateValidite = date('d/m/Y', strtotime("+30 day")); -// 30 jours par défaut + // 30 jours par défaut } $ImageFond = imagecreate(120, 40); @@ -361,7 +362,7 @@ trait GenerateBR $black = imagecolorallocate($ImageFond, 0, 0, 0); imagettftext($ImageFond, 11, 0, 0, 14, $black, $arial, $codeBarre); imagecopy($mask, $ImageFond, 52, 390, 0, 0, imagesx($ImageFond), imagesy($ImageFond)); - }//end if + } //end if ob_start(); imagejpeg($mask, null, 100); @@ -378,57 +379,51 @@ trait GenerateBR // VB 22/04/2021 public function printCoupon(Coupon $coupon, Customers $customer, $crmInfos, Campagne $campagne) { - $arial = public_path() . '/build/assets/fonts/coupons/arial.ttf'; + $arial = public_path() . '/build/assets/fonts/coupons/arial.ttf'; $arialbd = public_path() . '/build/assets/fonts/coupons/arialbd.ttf'; - // ---- Masque dans lequel l'image finale sera placée - $mask = imagecreatefrompng($coupon->coupon_image); + // Load base mask image + $mask = imagecreatefrompng($coupon->coupon_image); list($maskWidth, $maskHeight) = getimagesize($coupon->coupon_image); - - // ----- Mentions légales + RCS dépendant de l'user + // --- Draw Legal Mentions + RCS depending on the user country if ($coupon->coupon_legal_mentions_splitted == 1) { $legalMentions = explode('', $coupon->coupon_legal_mentions); + if ($customer->country !== 250) { // BELGIQUE - Luxembourg - $txt_mentions = $legalMentions[0] . "\n\r" . $legalMentions[1] . " " . $coupon->coupon_rcs; - $ImageMentionFond = imagecreate(420, 145); - $boxMentions = imagettfbbox(7, 0, $arial, $txt_mentions); - $white = imagecolorallocate($ImageMentionFond, 255, 255, 255); - imagefilledrectangle($ImageMentionFond, 0, 0, 414, 99, $white); - $black = imagecolorallocate($ImageMentionFond, 0, 0, 0); - $lines_mentions = explode("\r\n", $txt_mentions); - $y = 19; - foreach ($lines_mentions as $line) { - $this->imagettftextSp($ImageMentionFond, 7, 0, 15, $y, $black, $arial, $line, 0.01); - $y = $y + 9; - } - // imagecolortransparent($ImageMentionFond, $white); - imagecopy($mask, $ImageMentionFond, 335, 235, 0, 0, imagesx($ImageMentionFond), imagesy($ImageMentionFond)); + $txt_mentions = $legalMentions[0] . "\n\r " . $legalMentions[1] . "\n\r " . $coupon->coupon_rcs; + $imageWidth = 420; + $imageHeight = 145; } else { - // FIXME : Semble non utilise ici // FR - $txt_mentions = $legalMentions[0] . " " . $coupon->coupon_rcs; - $ImageMentionFond = imagecreate(415, 100); - $boxMentions = imagettfbbox(7, 0, $arial, $txt_mentions); - $white = imagecolorallocate($ImageMentionFond, 255, 255, 255); - imagefilledrectangle($ImageMentionFond, 0, 0, 414, 99, $white); - $black = imagecolorallocate($ImageMentionFond, 0, 0, 0); - $lines_mentions = explode("\r\n", $txt_mentions); - $y = 19; - foreach ($lines_mentions as $line) { - $this->imagettftextSp($ImageMentionFond, 7, 0, 15, $y, $black, $arial, $line, 0.01); - $y = $y + 9; - } - // imagecolortransparent($ImageMentionFond, $white); - imagecopy($mask, $ImageMentionFond, 335, 235, 0, 0, imagesx($ImageMentionFond), imagesy($ImageMentionFond)); - }//end if - }//end if + $txt_mentions = $legalMentions[0] . " " . $coupon->coupon_rcs; + $imageWidth = 415; + $imageHeight = 100; + } + $ImageMentionFond = imagecreate($imageWidth, $imageHeight); + $white = imagecolorallocate($ImageMentionFond, 255, 255, 255); + imagefilledrectangle($ImageMentionFond, 0, 0, $imageWidth - 6, $imageHeight - 46, $white); + $black = imagecolorallocate($ImageMentionFond, 0, 0, 0); - // ---- Inscription de l'utilisateur et de la ville - $libelle = "OFFRE RÉSERVÉE À"; - $user = sprintf( + $cleanedString = str_replace(["\r", "\n"], '', $txt_mentions); + $lines_mentions = str_split($cleanedString, 92); + + $y = 19; + foreach ($lines_mentions as $line) { + $this->imagettftextSp($ImageMentionFond, 7, 0, 15, $y, $black, $arial, $line, 0.01); + $y += 9; + } + + imagecopy($mask, $ImageMentionFond, 335, 235, 0, 0, imagesx($ImageMentionFond), imagesy($ImageMentionFond)); + + imagedestroy($ImageMentionFond); + } + + // --- User inscription and city + $libelle = "OFFRE RÉSERVÉE À"; + $user = sprintf( "%s\r\n%s\r\n%s\r\n%s %s\r\n%s", $crmInfos->firstname, $crmInfos->lastname, @@ -437,124 +432,135 @@ trait GenerateBR $crmInfos->city, sprintf('id #%s', str_pad($customer->id, 13, "0", STR_PAD_LEFT)) ); - $ImageFond = imagecreate(120, 80); - $white = imagecolorallocate($ImageFond, 255, 255, 255); - $black = imagecolorallocate($ImageFond, 0, 0, 0); - $box2 = imagettfbbox(8, 0, $arial, $libelle); - imagettftext($ImageFond, 8, 0, (120 / 2) - (abs($box2[4]) - abs($box2[6])) / 2, 10, $black, $arialbd, $libelle); - // Write line by line. + $ImageFond = imagecreate(120, 80); + $white = imagecolorallocate($ImageFond, 255, 255, 255); + $black = imagecolorallocate($ImageFond, 0, 0, 0); + + // Centered libelle text + $boxLibelle = imagettfbbox(8, 0, $arial, $libelle); + $libelleX = (120 / 2) - (abs($boxLibelle[4]) - abs($boxLibelle[6])) / 2; + imagettftext($ImageFond, 8, 0, $libelleX, 10, $black, $arialbd, $libelle); + + // User lines $userLines = explode("\r\n", $user); - $y = 25; + $y = 25; foreach ($userLines as $line) { $box = imagettfbbox(8, 0, $arial, $line); - $this->imagettftextSp($ImageFond, 8, 0, (120 / 2) - (abs($box[4]) - abs($box[6])) / 2, $y, $black, $arial, $line, 0.01); - $y = $y + 10; + $lineX = (120 / 2) - (abs($box[4]) - abs($box[6])) / 2; + $this->imagettftextSp($ImageFond, 8, 0, $lineX, $y, $black, $arial, $line, 0.01); + $y += 10; } imagecolortransparent($ImageFond, $white); imagecopy($mask, $ImageFond, 770, 40, 0, 0, imagesx($ImageFond), imagesy($ImageFond)); + imagedestroy($ImageFond); - // ---- Inscription de la date de validité + // --- Validity date inscription $libelle = "Valable jusqu'au :"; - - if ($coupon->coupon_validity != 0) { - $dateValidite = date('d/m/Y', strtotime("+" . $coupon->coupon_validity . " day")); - } else { - $dateValidite = date('d/m/Y', strtotime("+30 day")); - // 30 jours par défaut - } + $validDays = ($coupon->coupon_validity != 0) ? $coupon->coupon_validity : 30; + $dateValidite = date('d/m/Y', strtotime("+$validDays day")); $ImageFond = imagecreate(120, 40); - $white = imagecolorallocate($ImageFond, 255, 255, 255); - $black = imagecolorallocate($ImageFond, 0, 0, 0); + $white = imagecolorallocate($ImageFond, 255, 255, 255); + $black = imagecolorallocate($ImageFond, 0, 0, 0); + imagettftext($ImageFond, 8, 0, 1, 8, $black, $arialbd, $libelle); imagettftext($ImageFond, 8, 0, 20, 23, $black, $arial, $dateValidite); + imagecolortransparent($ImageFond, $white); imagecopy($mask, $ImageFond, 780, 142, 0, 0, imagesx($ImageFond), imagesy($ImageFond)); + imagedestroy($ImageFond); - // Save printed coupon. + // Save printed coupon $printedCoupon = $this->savePrintedCoupon($campagne, $customer, $coupon); - // Right barcode + // --- Right barcode $codeBarre = str_pad($printedCoupon->id, 13, "0", STR_PAD_LEFT); $generator = new \Picqer\Barcode\BarcodeGeneratorPNG(); - $base64 = $generator->getBarcode($codeBarre, $generator::TYPE_CODE_128_A, 2, 46); + $base64 = $generator->getBarcode($codeBarre, $generator::TYPE_CODE_128, 2, 30); $barcodeImage = imagecreatefromstring($base64); - imagecopy($mask, $barcodeImage, 540, 342, 0, 0, imagesx($barcodeImage), imagesy($barcodeImage)); - // Text + + imagecopy($mask, $barcodeImage, 660, 355, 0, 0, imagesx($barcodeImage), imagesy($barcodeImage)); + imagedestroy($barcodeImage); + + // Text below right barcode $ImageFond = imagecreate(380, 15); $white = imagecolorallocate($ImageFond, 255, 255, 255); $black = imagecolorallocate($ImageFond, 0, 0, 0); imagettftext($ImageFond, 11, 0, 0, 14, $black, $arial, $codeBarre); - imagecopy($mask, $ImageFond, 795, 390, 0, 0, imagesx($ImageFond), imagesy($ImageFond)); - // End text - // End right barcode + imagecopy($mask, $ImageFond, 800, 385, 0, 0, imagesx($ImageFond), imagesy($ImageFond)); + imagedestroy($ImageFond); - // ---- Création code barre + // --- Barcode treatment text if ($customer->country !== 250) { $textReduc = "Traitement ScanCoupon/HighCo Data Benelux"; + $copyPosition = [70, 230]; } elseif ($coupon->coupon_treatment_center == 0) { $textReduc = "• Traitement ScanCoupon •"; + $copyPosition = [52, 322]; } else { $textReduc = "• Traitement Sogec •"; + $copyPosition = [52, 322]; } $ImageFond = imagecreate(270, 20); - $white = imagecolorallocate($ImageFond, 255, 255, 255); - $black = imagecolorallocate($ImageFond, 0, 0, 0); + $white = imagecolorallocate($ImageFond, 255, 255, 255); + $black = imagecolorallocate($ImageFond, 0, 0, 0); imagettftext($ImageFond, 9, 0, 0, 10, $black, $arialbd, $textReduc); - if ($customer->country !== 250) { - imagecopy($mask, $ImageFond, 70, 230, 0, 0, imagesx($ImageFond), imagesy($ImageFond)); - } else { - imagecopy($mask, $ImageFond, 52, 322, 0, 0, imagesx($ImageFond), imagesy($ImageFond)); - } + imagecopy($mask, $ImageFond, $copyPosition[0], $copyPosition[1], 0, 0, imagesx($ImageFond), imagesy($ImageFond)); + imagedestroy($ImageFond); - // Left barcode + // --- Left barcode if ($customer->country !== 250) { - $codeBarre = imagecreatefromjpeg($coupon->coupon_gs1code); - imageresolution($codeBarre, 72); + $codeBarreImg = imagecreatefromjpeg($coupon->coupon_gs1code); + imageresolution($codeBarreImg, 72); list($width, $height) = getimagesize($coupon->coupon_gs1code); - $r = $width / $height; - $w = 285; - $h = 150; - if ($w / $h > $r) { - $newwidth = $h * $r; - $newheight = $h; - } else { - $newheight = $w / $r; - $newwidth = $w; - } - $test = imagecreatetruecolor($newwidth, $newheight); - imagecopyresampled($test, $codeBarre, 0, 0, 0, 0, $newwidth, $newheight, $width, $height); + $ratio = $width / $height; + $maxWidth = 285; + $maxHeight = 150; - imagecopy($mask, $test, 60, 250, 0, 0, imagesx($test), imagesy($test)); + if ($maxWidth / $maxHeight > $ratio) { + $newWidth = $maxHeight * $ratio; + $newHeight = $maxHeight; + } else { + $newHeight = $maxWidth / $ratio; + $newWidth = $maxWidth; + } + + $resizedImage = imagecreatetruecolor($newWidth, $newHeight); + imagecopyresampled($resizedImage, $codeBarreImg, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height); + imagecopy($mask, $resizedImage, 60, 250, 0, 0, $newWidth, $newHeight); + imagedestroy($codeBarreImg); + imagedestroy($resizedImage); } else { - $codeBarre = $coupon->coupon_code; - $generator = new \Picqer\Barcode\BarcodeGeneratorPNG(); + $codeBarre = $coupon->coupon_code; + $generator = new \Picqer\Barcode\BarcodeGeneratorPNG(); $base64 = $generator->getBarcode($codeBarre, $generator::TYPE_CODE_128_A, 2, 46); $barcodeImage = imagecreatefromstring($base64); imagecopy($mask, $barcodeImage, 10, 342, 0, 0, imagesx($barcodeImage), imagesy($barcodeImage)); + imagedestroy($barcodeImage); + $ImageFond = imagecreate(380, 15); - $white = imagecolorallocate($ImageFond, 255, 255, 255); - $black = imagecolorallocate($ImageFond, 0, 0, 0); + $white = imagecolorallocate($ImageFond, 255, 255, 255); + $black = imagecolorallocate($ImageFond, 0, 0, 0); imagettftext($ImageFond, 11, 0, 0, 14, $black, $arial, $codeBarre); imagecopy($mask, $ImageFond, 52, 390, 0, 0, imagesx($ImageFond), imagesy($ImageFond)); - }//end if - // End left barcode + imagedestroy($ImageFond); + } + // Final output as base64 encoded JPEG ob_start(); imagejpeg($mask, null, 100); $contents = ob_get_contents(); ob_end_clean(); - $dataUri = "data:image/jpeg;base64," . base64_encode($contents); - // ---- Libère la mémoire associée aux images manipulées imagedestroy($mask); - imagedestroy($ImageFond); - return $dataUri; + + return "data:image/jpeg;base64," . base64_encode($contents); } + public function savePrintedCoupon(Campagne $campagne, Customers $customer, Coupon $coupon): PrintedCoupons { // ----- mémorise l'impression @@ -566,4 +572,9 @@ trait GenerateBR return $mur; } + + private function addMarketingDirectCode() + { + + } } diff --git a/config/crm.php b/config/crm.php deleted file mode 100644 index 4cc5fa5..0000000 --- a/config/crm.php +++ /dev/null @@ -1,5 +0,0 @@ - env('CRM_ENDPOINT') -]; diff --git a/config/services.php b/config/services.php index 0751cb1..aee46cf 100644 --- a/config/services.php +++ b/config/services.php @@ -36,4 +36,8 @@ return [ 'secret' => env('STRIPE_SECRET'), ], + 'crm' => [ + 'endpoint' => env('CRM_ENDPOINT') + ] + ]; diff --git a/database/migrations/2025_10_30_103126_alter_ref_column_on_basket_table.php b/database/migrations/2025_10_30_103126_alter_ref_column_on_basket_table.php new file mode 100644 index 0000000..1ac970d --- /dev/null +++ b/database/migrations/2025_10_30_103126_alter_ref_column_on_basket_table.php @@ -0,0 +1,34 @@ +renameColumn('ref', 'uuid'); + $table->unique('uuid'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('baskets', function (Blueprint $table){ + $table->dropUnique(['uuid']); + $table->renameColumn('uuid', 'ref'); + }); + } +} diff --git a/package.json b/package.json index 305947a..8444e3b 100644 --- a/package.json +++ b/package.json @@ -35,8 +35,5 @@ "sweetalert": "^2.0.8", "vue": "^2.5.7", "webpack-cli": "^6.0.1" - }, - "dependencies": { - "node-sass": "^6.0.1" } } diff --git a/resources/views/fo/basket/show.blade.php b/resources/views/fo/basket/show.blade.php new file mode 100644 index 0000000..d83cdc0 --- /dev/null +++ b/resources/views/fo/basket/show.blade.php @@ -0,0 +1,49 @@ + + + + + {{ $basket->campagne->site->site_libelle }} - {{ __('Bons de Réduction') }} + + + + + +
+
+ {{ $basket->campagne->site->site_libelle }} +
+ +
+
+
Nombre de coupons :
+

{{ $totalQuantity }}

+
+
+ @if($totalQuantity) +
+

Liste des coupons :

+
    + @foreach ($basket->contents as $content) + @php + $remain = $content->quantity - $content->printed; + @endphp + @if($remain) +
  • + Nom : {{ $content->coupon->coupon_name }}
    + Quantité : {{ $remain }} +
  • + @endif + @endforeach +
+
+ + + @endif + +
+ + + + diff --git a/resources/views/pdf/coupons.blade.php b/resources/views/pdf/coupons.blade.php index 5888197..5bad066 100644 --- a/resources/views/pdf/coupons.blade.php +++ b/resources/views/pdf/coupons.blade.php @@ -13,10 +13,13 @@
+ @php + $total = count($coupons); + @endphp @foreach($coupons as $k => $coupon)
- @if (($k + 1) % 3 === 0) + @if (($k + 1) % 3 === 0 && $total > 3)
@endif @endforeach diff --git a/routes/api.php b/routes/api.php index 0c69088..4dfa3da 100644 --- a/routes/api.php +++ b/routes/api.php @@ -15,7 +15,6 @@ Route::prefix('v1') ->group(function () { Route::get('/customer/mur', 'Api\v1\CustomerController@mur'); Route::get('/customer/basket', 'Api\v1\CustomerController@basket'); - Route::get('/customer/basket/print', 'Api\v1\CustomerController@printBasket'); Route::get('/customer/basket/update', 'Api\v1\BasketController@updateBasket'); Route::resource('customer', 'Api\v1\CustomerController'); Route::get('/mur', 'Api\v1\MurController@show'); diff --git a/routes/channels.php b/routes/channels.php deleted file mode 100644 index 1e36a53..0000000 --- a/routes/channels.php +++ /dev/null @@ -1,16 +0,0 @@ -id === (int)$id; -}); diff --git a/routes/console.php b/routes/console.php deleted file mode 100644 index 23c267d..0000000 --- a/routes/console.php +++ /dev/null @@ -1,18 +0,0 @@ -comment(Inspiring::quote()); -})->describe('Display an inspiring quote'); diff --git a/routes/web.php b/routes/web.php index b71c6fa..a6099fd 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,5 +1,6 @@ group(function() { + Route::name('basket.')->prefix('basket')->group(function () { + Route::get('/{basket:uuid}', [BasketController::class, 'show'])->name('show'); + Route::get('/{basket:uuid}/print', [BasketController::class, 'print'])->name('print'); + }); +}); From 619a859677b4df1dbfb020b07a19efb4995b18df Mon Sep 17 00:00:00 2001 From: Noah Lancien Date: Thu, 6 Nov 2025 10:18:44 +0100 Subject: [PATCH 5/8] fix: renamed ref -> uuid when a new basket is created --- app/Http/Controllers/Api/v1/BasketController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/v1/BasketController.php b/app/Http/Controllers/Api/v1/BasketController.php index c7c0da0..b18e2e0 100644 --- a/app/Http/Controllers/Api/v1/BasketController.php +++ b/app/Http/Controllers/Api/v1/BasketController.php @@ -146,7 +146,7 @@ class BasketController extends Controller } } else { $basket = new Basket(); - $basket->ref = (string) Str::uuid(); + $basket->uuid = (string) Str::uuid(); $basket->campagne()->associate($refcamp); $basket->customer()->associate($customer); $basket->save(); From 9d711289d3f9ec2cae480c0e2afaccbff56b9c81 Mon Sep 17 00:00:00 2001 From: Noah Lancien Date: Tue, 4 Nov 2025 15:29:03 +0100 Subject: [PATCH 6/8] feat: added archive for stats --- app/Console/Commands/ArchiveStatsCommand.php | 49 +++++++++ app/Console/Kernel.php | 4 + app/Jobs/ArchiveStatsJob.php | 67 +++++++++++ app/Lib/Helpers/ToolboxHelper.php | 104 ++++++++++++++++++ app/Models/ArchiveCampaign.php | 82 ++++++++++++++ app/Models/ArchiveCoupon.php | 60 ++++++++++ app/Models/ArchiveSite.php | 60 ++++++++++ app/Models/ArchiveStat.php | 91 +++++++++++++++ app/Statistique.php | 32 +++++- app/Traits/GenerateBR.php | 38 ++++++- app/Traits/HasUniqueField.php | 31 ++++++ ..._10_30_160644_alter_statistiques_table.php | 70 ++++++++++++ ...1_03_150025_create_archive_sites_table.php | 33 ++++++ ...03_150033_create_archive_coupons_table.php | 33 ++++++ ..._150048_create_archive_campaigns_table.php | 34 ++++++ ...1_03_150056_create_archive_stats_table.php | 38 +++++++ 16 files changed, 820 insertions(+), 6 deletions(-) create mode 100644 app/Console/Commands/ArchiveStatsCommand.php create mode 100644 app/Jobs/ArchiveStatsJob.php create mode 100644 app/Lib/Helpers/ToolboxHelper.php create mode 100644 app/Models/ArchiveCampaign.php create mode 100644 app/Models/ArchiveCoupon.php create mode 100644 app/Models/ArchiveSite.php create mode 100644 app/Models/ArchiveStat.php create mode 100644 app/Traits/HasUniqueField.php create mode 100644 database/migrations/2025_10_30_160644_alter_statistiques_table.php create mode 100644 database/migrations/2025_11_03_150025_create_archive_sites_table.php create mode 100644 database/migrations/2025_11_03_150033_create_archive_coupons_table.php create mode 100644 database/migrations/2025_11_03_150048_create_archive_campaigns_table.php create mode 100644 database/migrations/2025_11_03_150056_create_archive_stats_table.php diff --git a/app/Console/Commands/ArchiveStatsCommand.php b/app/Console/Commands/ArchiveStatsCommand.php new file mode 100644 index 0000000..cac13ba --- /dev/null +++ b/app/Console/Commands/ArchiveStatsCommand.php @@ -0,0 +1,49 @@ +handle(); + + $this->info(sprintf( + '%s archive(s) created', + ArchiveStat::count() - $baseArchiveStatCount + )); + $this->info(sprintf( + '%s stat(s) deleted', + $baseStatCount - Statistique::count() + )); + $this->info('Stats archived 👌'); + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 206dd09..3db3f7a 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -6,6 +6,7 @@ use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; use App\Site; use App\Campagne; +use App\Jobs\ArchiveStatsJob; use App\Jobs\CleanOldDatasJob; use App\User; use App\Notification; @@ -35,6 +36,9 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule) { + // Archive stats every day at 2:00 AM (to prevent time difference between the server and the reality). + $schedule->job(ArchiveStatsJob::class)->dailyAt('2:00'); + // Clean datas older than 3 years every day at midnight. $schedule->job(CleanOldDatasJob::class)->daily(); diff --git a/app/Jobs/ArchiveStatsJob.php b/app/Jobs/ArchiveStatsJob.php new file mode 100644 index 0000000..d9840ab --- /dev/null +++ b/app/Jobs/ArchiveStatsJob.php @@ -0,0 +1,67 @@ +where('date', '<', now()) + ->each(function ($stat) { + $archiveStat = new ArchiveStat(); + $archiveStat->quantity = $stat->stat_couponPrinted; + $archiveStat->date = $stat->date; + $archiveStat->archive_coupon_id = $stat->stat_couponId; + $archiveStat->archive_campaign_id = $stat->stat_campId; + $archiveStat->saveOrFail(); + }); + + Statistique::query() + ->where('date', '<', now()) + ->delete(); + }); + } catch (\Exception $e) { + Log::error( + sprintf( + "ArchiveStatsJob failed - Code : %s - %s", + $e->getCode(), + $e->getMessage(), + ) + ); + \report($e); + } //end try + } +} diff --git a/app/Lib/Helpers/ToolboxHelper.php b/app/Lib/Helpers/ToolboxHelper.php new file mode 100644 index 0000000..6238a46 --- /dev/null +++ b/app/Lib/Helpers/ToolboxHelper.php @@ -0,0 +1,104 @@ + $value], [ + self::mbReplace('.', '\.', $field) => Rule::unique($table, $field)->ignore($id), + ], [ + self::mbReplace('.', '\.', "{$field}.unique") + => trans('crud.libs.toolbox.assertFieldIsUnique', ['attribute' => $field, 'value' => $value]) + ])->validate(); + return; + } + Validator::make([$field => $value], [ + $field => Rule::unique($table)->where($query)->ignore($id), + ], [ + $field . '.unique' => trans( + 'crud.libs.toolbox.assertFieldIsUnique', + ['attribute' => $field, 'value' => $value] + ) + ])->validate(); + } +} diff --git a/app/Models/ArchiveCampaign.php b/app/Models/ArchiveCampaign.php new file mode 100644 index 0000000..7560b71 --- /dev/null +++ b/app/Models/ArchiveCampaign.php @@ -0,0 +1,82 @@ + + */ + protected $fillable = [ + 'name', + 'campaign_id', + 'archive_site_id' + ]; + + /** + * Perform any actions required after the model boots. + * + * @return void + */ + protected static function booted(): void + { + static::creating(function (self $model) { + $model->assertFieldIsUnique('campaign_id', $model->campaign_id); + $site = Site::query()->where('site_id', $model->archive_site_id)->firstOrFail(); + $archiveSite = ArchiveSite::query()->firstOrCreate( + [ + 'site_id' => $model->archive_site_id + ], + [ + 'name' => $site->site_nom + ] + ); + $model->archiveSite()->associate($archiveSite); + }); + static::updating(function () { + throw new \RuntimeException('Updating an archived campaign is not authorized'); + }); + static::deleting(function () { + throw new \RuntimeException('Deleting an archived campaign is not authorized'); + }); + } + + // * RELATIONS + + /** + * ArchiveSite model relation + * @return Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function archiveSite() + { + return $this->belongsTo(ArchiveSite::class); + } + + /** + * ArchiveSite model relation + * @return Illuminate\Database\Eloquent\Relations\HasMany + */ + public function archiveStat() + { + return $this->hasMany(ArchiveStat::class); + } +} diff --git a/app/Models/ArchiveCoupon.php b/app/Models/ArchiveCoupon.php new file mode 100644 index 0000000..e93fd19 --- /dev/null +++ b/app/Models/ArchiveCoupon.php @@ -0,0 +1,60 @@ + + */ + protected $fillable = [ + 'name', + 'coupon_code', + ]; + + /** + * Perform any actions required after the model boots. + * + * @return void + */ + protected static function booted(): void + { + static::creating(function (self $model) { + $model->assertFieldIsUnique('coupon_code', $model->coupon_code); + }); + static::updating(function () { + throw new \RuntimeException('Updating an archived coupon is not authorized'); + }); + static::deleting(function () { + throw new \RuntimeException('Deleting an archived coupon is not authorized'); + }); + } + + // * RELATIONS + + /** + * ArchiveStat model relation + * @return Illuminate\Database\Eloquent\Relations\HasMany + */ + public function archiveStat() + { + return $this->hasMany(ArchiveStat::class); + } +} diff --git a/app/Models/ArchiveSite.php b/app/Models/ArchiveSite.php new file mode 100644 index 0000000..b19b48f --- /dev/null +++ b/app/Models/ArchiveSite.php @@ -0,0 +1,60 @@ + + */ + protected $fillable = [ + 'name', + 'site_id', + ]; + + /** + * Perform any actions required after the model boots. + * + * @return void + */ + protected static function booted(): void + { + static::creating(function (self $model) { + $model->assertFieldIsUnique('site_id', $model->site_id); + }); + static::updating(function (self $model) { + throw new \RuntimeException('Updating an archived site is not authorized'); + }); + static::deleting(function () { + throw new \RuntimeException('Deleting an archived site is not authorized'); + }); + } + + // * RELATIONS + + /** + * ArchiveCampaign model relation + * @return Illuminate\Database\Eloquent\Relations\HasMany + */ + public function archiveCampaign() + { + return $this->hasMany(ArchiveCampaign::class); + } +} diff --git a/app/Models/ArchiveStat.php b/app/Models/ArchiveStat.php new file mode 100644 index 0000000..5d26e17 --- /dev/null +++ b/app/Models/ArchiveStat.php @@ -0,0 +1,91 @@ + + */ + protected $fillable = [ + 'date', + 'quantity', + ]; + + /** + * Perform any actions required after the model boots. + * + * @return void + */ + protected static function booted(): void + { + static::creating(function (self $model) { + $campaign = Campagne::withTrashed()->where('camp_id', $model->archive_campaign_id)->firstOrFail(); + $archiveCampaign = ArchiveCampaign::query()->firstOrCreate( + [ + 'campaign_id' => $model->archive_campaign_id + ], + [ + 'name' => $campaign->camp_name, + 'archive_site_id' => $campaign->camp_siteId, + ] + ); + $model->archiveCampaign()->associate($archiveCampaign); + + $coupon = Coupon::withTrashed()->where('coupon_id', $model->archive_coupon_id)->firstOrFail(); + $archiveCoupon = ArchiveCoupon::query()->firstOrCreate( + [ + 'coupon_code' => $model->archive_coupon_id + ], + [ + 'name' => $coupon->coupon_code, + ] + ); + $model->archiveCoupon()->associate($archiveCoupon); + }); + static::updating(function () { + throw new \RuntimeException('Upating an archived stat is not authorized'); + }); + static::deleting(function () { + throw new \RuntimeException('Deleting an archived stat is not authorized'); + }); + } + + // * RELATIONS + + /** + * ArchiveCoupon model relation + * @return Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function archiveCoupon() + { + return $this->belongsTo(ArchiveCoupon::class); + } + + /** + * ArchiveCampaign model relation + * @return Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function archiveCampaign() + { + return $this->belongsTo(ArchiveCampaign::class); + } +} diff --git a/app/Statistique.php b/app/Statistique.php index 73de4b0..5c1ddef 100644 --- a/app/Statistique.php +++ b/app/Statistique.php @@ -25,9 +25,37 @@ class Statistique extends Model /** * @var array */ - protected $fillable = [ + protected $fillable = [ 'stat_campId', 'stat_couponId', - 'stat_couponPrinted' + 'stat_couponPrinted', + 'date' ]; + + /** + * @var array + */ + protected $casts = [ + 'date' => 'date' + ]; + + // * RELATIONS + + /** + * ArchiveCoupon model relation + * @return Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function campaign() + { + return $this->belongsTo(Campagne::class, 'stat_campId', 'camp_id'); + } + + /** + * ArchiveCampaign model relation + * @return Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function coupon() + { + return $this->belongsTo(Coupon::class, 'stat_couponId', 'coupon_id'); + } } diff --git a/app/Traits/GenerateBR.php b/app/Traits/GenerateBR.php index 20e5113..1184cea 100644 --- a/app/Traits/GenerateBR.php +++ b/app/Traits/GenerateBR.php @@ -8,6 +8,7 @@ use Illuminate\Http\Request; use App\Coupon; use App\Models\PrintedCoupons; use App\Site; +use App\Statistique; trait GenerateBR { @@ -557,13 +558,22 @@ trait GenerateBR imagedestroy($mask); + // Save stat. + $this->saveStat($campagne, $coupon); + return "data:image/jpeg;base64," . base64_encode($contents); } - + /** + * Save 'printed_coupons' line. + * + * @param Campagne $campagne + * @param Customers $customer + * @param Coupon $coupon + * @return PrintedCoupons + */ public function savePrintedCoupon(Campagne $campagne, Customers $customer, Coupon $coupon): PrintedCoupons { - // ----- mémorise l'impression $mur = new PrintedCoupons(); $mur->camp_id = $campagne->camp_id; $mur->customer_id = $customer->id; @@ -573,8 +583,28 @@ trait GenerateBR return $mur; } - private function addMarketingDirectCode() + /** + * Save stats in the 'statistiques' table. + * + * @param Campagne $campagne + * @param Coupon $coupon + * @return Statistique + */ + public function saveStat(Campagne $campagne, Coupon $coupon): Statistique { - + $stat = Statistique::query()->firstOrCreate( + [ + 'date' => now()->toDateString(), + 'stat_campId' => $campagne->camp_id, + 'stat_couponId' => $coupon->coupon_id, + ], + [ + 'stat_couponPrinted' => 0, + ] + ); + $stat->stat_couponPrinted += 1; + $stat->save(); + + return $stat; } } diff --git a/app/Traits/HasUniqueField.php b/app/Traits/HasUniqueField.php new file mode 100644 index 0000000..3fee366 --- /dev/null +++ b/app/Traits/HasUniqueField.php @@ -0,0 +1,31 @@ +getTable(); + ToolboxHelper::assertFieldIsUnique($table, $field, $value, $id); + + if (!is_null($slugField)) { + ToolboxHelper::assertFieldIsUnique($table, $slugField, Str::slug($value), $id); + } + } + } +} diff --git a/database/migrations/2025_10_30_160644_alter_statistiques_table.php b/database/migrations/2025_10_30_160644_alter_statistiques_table.php new file mode 100644 index 0000000..5b0344d --- /dev/null +++ b/database/migrations/2025_10_30_160644_alter_statistiques_table.php @@ -0,0 +1,70 @@ +date('date')->nullable()->after('stat_couponPrinted'); + }); + + // Fill the previously created column with created_at date. + DB::table('statistiques')->update([ + 'date' => DB::raw('DATE(created_at)') + ]); + + // Remove nullable option. + Schema::table('statistiques', function (Blueprint $table) { + $table->date('date')->nullable(false)->change(); + }); + + // Create a temp table with only groupped datas. + DB::statement('CREATE TEMPORARY TABLE temp_statistiques AS + SELECT stat_campId, stat_couponId, date, SUM(stat_couponPrinted) AS quantity + FROM statistiques + GROUP BY stat_campId, stat_couponId, date'); + + // Remove all datas from the origin table. + DB::table('statistiques')->truncate(); + + // Insert datas from the temp table to the old one. + DB::statement('INSERT INTO statistiques (stat_campId, stat_couponId, date, stat_couponPrinted) + SELECT stat_campId, stat_couponId, date, quantity FROM temp_statistiques'); + + Schema::table('statistiques', function (Blueprint $table) { + $table->foreign('stat_campId')->references('camp_id')->on('campagne'); + $table->foreign('stat_couponId')->references('coupon_id')->on('coupon'); + $table->unique(['stat_campId', 'stat_couponId', 'date']); + }); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + DB::transaction(function () { + Schema::table('statistiques', function (Blueprint $table) { + $table->dropColumn('date'); + $table->dropForeign('statistiques_stat_campid_foreign'); + $table->dropForeign('statistiques_stat_couponid_foreign'); + $table->dropUnique('statistiques_stat_campid_stat_couponid_date_unique'); + }); + }); + } +} diff --git a/database/migrations/2025_11_03_150025_create_archive_sites_table.php b/database/migrations/2025_11_03_150025_create_archive_sites_table.php new file mode 100644 index 0000000..55939a3 --- /dev/null +++ b/database/migrations/2025_11_03_150025_create_archive_sites_table.php @@ -0,0 +1,33 @@ +id(); + $table->string('name')->comment('Name of the archived site'); + $table->string('site_id', 50)->unique()->comment('Id of the archived site'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('archive_sites'); + } +} diff --git a/database/migrations/2025_11_03_150033_create_archive_coupons_table.php b/database/migrations/2025_11_03_150033_create_archive_coupons_table.php new file mode 100644 index 0000000..21d2c8d --- /dev/null +++ b/database/migrations/2025_11_03_150033_create_archive_coupons_table.php @@ -0,0 +1,33 @@ +id(); + $table->string('name', 500)->comment('Name of the archived coupon'); + $table->string('coupon_code', 50)->unique()->comment('Treatment code of the archived coupon'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('archive_coupons'); + } +} diff --git a/database/migrations/2025_11_03_150048_create_archive_campaigns_table.php b/database/migrations/2025_11_03_150048_create_archive_campaigns_table.php new file mode 100644 index 0000000..94ea7a1 --- /dev/null +++ b/database/migrations/2025_11_03_150048_create_archive_campaigns_table.php @@ -0,0 +1,34 @@ +id(); + $table->string('name', 500)->comment('Name of the archived campaign'); + $table->unsignedBigInteger('campaign_id')->unique()->comment('Id of the archived campaign'); + $table->foreignId('archive_site_id')->constrained(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('archive_campaigns'); + } +} diff --git a/database/migrations/2025_11_03_150056_create_archive_stats_table.php b/database/migrations/2025_11_03_150056_create_archive_stats_table.php new file mode 100644 index 0000000..0a17dc2 --- /dev/null +++ b/database/migrations/2025_11_03_150056_create_archive_stats_table.php @@ -0,0 +1,38 @@ +id(); + $table->unsignedInteger('quantity')->comment('Quantity of distributed coupons this day'); + $table->date('date'); + $table->foreignId('archive_coupon_id')->constrained(); + $table->foreignId('archive_campaign_id')->constrained(); + $table->unique(['date', 'archive_coupon_id', 'archive_campaign_id']); + $table->timestamps(); + }); + + + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('archive_stats'); + } +} From 6efafddeb11d0348f7ff7339fd585335a5a95766 Mon Sep 17 00:00:00 2001 From: Vincent BRIDIER Date: Thu, 6 Nov 2025 16:17:22 +0100 Subject: [PATCH 7/8] cleeeeaaaaannnnnnnnnnn --- app/Enums/LogActions.php | 3 - app/Http/Controllers/AjaxController.php | 11 +- app/Http/Controllers/Auth/LoginController.php | 2 +- .../Controllers/Bo/MaintenanceController.php | 29 ++ app/Http/Controllers/Bo/UserController.php | 76 +++++ app/Http/Controllers/CampagneController.php | 82 ----- app/Http/Controllers/ContactController.php | 204 ----------- app/Http/Controllers/CustomersController.php | 33 -- app/Http/Controllers/ExportController.php | 86 ----- .../Controllers/MaintenanceController.php | 88 ----- app/Http/Controllers/MurController.php | 315 ----------------- app/Http/Controllers/RedirectController.php | 6 +- .../Controllers/StatistiqueController.php | 93 ----- app/Http/Kernel.php | 4 - app/Http/Middleware/Admin.php | 27 -- app/Http/Middleware/Marketing.php | 20 -- app/Http/Middleware/ProcessSpecificUrl.php | 96 ------ .../Middleware/RedirectIfAuthenticated.php | 2 +- app/Http/Middleware/SaveCurrentRoute.php | 26 +- app/Http/Middleware/SuperAdmin.php | 25 -- app/Http/Middleware/VerifyCsrfToken.php | 1 - .../Users/DestroyUserRequest.php} | 9 +- .../Users/StoreUserRequest.php} | 7 +- .../Users/UpdateUserRequest.php} | 7 +- app/Http/Requests/Contact/SearchContact.php | 38 --- app/Http/Requests/Contact/StoreContact.php | 38 --- app/Http/Requests/Contact/UpdateContact.php | 42 --- app/Traits/GraphTrait.php | 76 ----- composer.json | 10 +- composer.lock | 73 +--- config/constants.php | 1 - resources/js/app.js | 14 - .../views/bo/maintenance/index.blade.php | 54 +++ .../views/{user => bo/users}/create.blade.php | 2 +- .../views/{user => bo/users}/edit.blade.php | 2 +- resources/views/bo/users/index.blade.php | 51 +++ resources/views/contact/create.blade.php | 66 ---- resources/views/contact/edit.blade.php | 281 --------------- resources/views/contact/index.blade.php | 111 ------ resources/views/customers/index.blade.php | 80 ----- resources/views/export/index.blade.php | 73 ---- resources/views/export/template.blade.php | 47 --- resources/views/layouts/header.blade.php | 2 +- resources/views/layouts/menu.blade.php | 20 +- resources/views/maintenance/index.blade.php | 104 ------ resources/views/stats/detail.blade.php | 323 ------------------ resources/views/stats/index.blade.php | 87 ----- resources/views/widgets/mur.blade.php | 64 ---- resources/views/widgets/panier.blade.php | 64 ---- routes/web.php | 87 ++--- 50 files changed, 283 insertions(+), 2779 deletions(-) create mode 100644 app/Http/Controllers/Bo/MaintenanceController.php create mode 100644 app/Http/Controllers/Bo/UserController.php delete mode 100644 app/Http/Controllers/CustomersController.php delete mode 100644 app/Http/Controllers/ExportController.php delete mode 100644 app/Http/Controllers/MaintenanceController.php delete mode 100644 app/Http/Controllers/MurController.php delete mode 100644 app/Http/Controllers/StatistiqueController.php delete mode 100644 app/Http/Middleware/Admin.php delete mode 100644 app/Http/Middleware/Marketing.php delete mode 100644 app/Http/Middleware/ProcessSpecificUrl.php delete mode 100644 app/Http/Middleware/SuperAdmin.php rename app/Http/Requests/{Contact/LoadWidgetContact.php => Bo/Users/DestroyUserRequest.php} (57%) rename app/Http/Requests/{Users/StoreUsers.php => Bo/Users/StoreUserRequest.php} (72%) rename app/Http/Requests/{Users/UpdateUsers.php => Bo/Users/UpdateUserRequest.php} (73%) delete mode 100644 app/Http/Requests/Contact/SearchContact.php delete mode 100644 app/Http/Requests/Contact/StoreContact.php delete mode 100644 app/Http/Requests/Contact/UpdateContact.php delete mode 100644 app/Traits/GraphTrait.php create mode 100644 resources/views/bo/maintenance/index.blade.php rename resources/views/{user => bo/users}/create.blade.php (95%) rename resources/views/{user => bo/users}/edit.blade.php (95%) create mode 100644 resources/views/bo/users/index.blade.php delete mode 100644 resources/views/contact/create.blade.php delete mode 100644 resources/views/contact/edit.blade.php delete mode 100644 resources/views/contact/index.blade.php delete mode 100644 resources/views/customers/index.blade.php delete mode 100644 resources/views/export/index.blade.php delete mode 100644 resources/views/export/template.blade.php delete mode 100644 resources/views/maintenance/index.blade.php delete mode 100644 resources/views/stats/detail.blade.php delete mode 100644 resources/views/stats/index.blade.php delete mode 100644 resources/views/widgets/mur.blade.php delete mode 100644 resources/views/widgets/panier.blade.php diff --git a/app/Enums/LogActions.php b/app/Enums/LogActions.php index e2ad939..3b639dd 100644 --- a/app/Enums/LogActions.php +++ b/app/Enums/LogActions.php @@ -6,7 +6,6 @@ use Kwaadpepper\Enum\BaseEnum; /** * @method static self updatebasket() - * @method static self delctrlcontact() * @method static self impression() * @method static self updatecontact() */ @@ -17,7 +16,6 @@ class LogActions extends BaseEnum { return [ 'updatebasket' => 1, - 'delctrlcontact' => 2, 'impression' => 3, 'updatecontact' => 4, ]; @@ -27,7 +25,6 @@ class LogActions extends BaseEnum { return [ 'updatebasket' => 'Mise à jour du panier', - 'delctrlcontact' => 'Réinitialisation du mur', 'impression' => 'Impression', 'updatecontact' => 'Mise à jour du contact', ]; diff --git a/app/Http/Controllers/AjaxController.php b/app/Http/Controllers/AjaxController.php index 3ccd2d4..8814d85 100644 --- a/app/Http/Controllers/AjaxController.php +++ b/app/Http/Controllers/AjaxController.php @@ -309,16 +309,7 @@ class AjaxController extends Controller die(); } - if ($request->session()->has('contact')) { - $contact = $request->session()->get('contact'); - } - - if ($campagne->camp_type->equals(CampaignTypes::mur()) || $campagne->camp_type->equals(CampaignTypes::murss())) { - if (!isset($contact)) { - die(); - } - return redirect(url('/') . '/?refsite=' . $campagne->camp_siteId . '&refcamp=' . $campagne->camp_id . '&hash=' . $contact->contact_hash); - } elseif ($campagne->camp_type->equals(CampaignTypes::panier()) || $campagne->camp_type->equals(CampaignTypes::paniers())) { + if ($campagne->camp_type->equals(CampaignTypes::panier()) || $campagne->camp_type->equals(CampaignTypes::paniers())) { if ($request->session()->has('panier')) { $panier = $request->session()->get('panier'); } else { diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 134286c..1677ff8 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -44,6 +44,6 @@ class LoginController extends Controller */ protected function authenticated() { - return redirect()->route('home'); + return redirect('/'); } } diff --git a/app/Http/Controllers/Bo/MaintenanceController.php b/app/Http/Controllers/Bo/MaintenanceController.php new file mode 100644 index 0000000..73a7752 --- /dev/null +++ b/app/Http/Controllers/Bo/MaintenanceController.php @@ -0,0 +1,29 @@ +where('log_statut', 'NOK') + ->where('created_at', '>=', Carbon::now()->subMonths(5)->startOfMonth()) + ->groupBy('month') + ->orderBy('month', 'asc') + ->get(); + + $logs = $datas->toArray(); + + return view('bo.maintenance.index', compact('logs')); + } +} diff --git a/app/Http/Controllers/Bo/UserController.php b/app/Http/Controllers/Bo/UserController.php new file mode 100644 index 0000000..4db6a77 --- /dev/null +++ b/app/Http/Controllers/Bo/UserController.php @@ -0,0 +1,76 @@ +with(['users' => $users, 'logs' => []]); + } + + public function create(Request $request) + { + $sites = Site::all(); + return view('bo.users.create', compact('sites')); + } + + public function store(StoreUserRequest $request) + { + $user = new User(); + $user->fill($request->validated()); + $user->password = Hash::make($request->password); + $user->save(); + + $sites = Site::whereIn('site_id', $request->site)->get()->pluck('id')->toArray(); + $user->sites()->sync($sites); + + return redirect()->route('bo.users.edit', $user); + } + + public function edit(User $user) + { + $sites = Site::all(); + $usersites = $user->sites()->get()->pluck('site_id')->toArray(); + + return view('bo.users.edit', compact('user', 'sites', 'usersites')); + } + + public function update(UpdateUserRequest $request, User $user) + { + $user->fill($request->validated()); + $user->save(); + + $sites = Site::whereIn('site_id', $request->site)->get()->pluck('id')->toArray(); + $user->sites()->sync($sites); + + $marques = Auth::user()->sites; + $submarques = $marques->map(function ($marque) { + return $marque->only(['id', 'site_nom', 'site_id', 'site_logo']); + }); + session()->put('marques', $submarques); + + return redirect()->route('bo.users.edit', $user); + } + + public function destroy(DestroyUserRequest $request, User $user) + { + if ($user->delete()) { + return redirect()->route('bo.users.index'); + } else { + session()->flash('delete_nok', 'Erreur lors de la suppression'); + return view('bo.user.index'); + } + } +} diff --git a/app/Http/Controllers/CampagneController.php b/app/Http/Controllers/CampagneController.php index 949d5a2..c9225a1 100644 --- a/app/Http/Controllers/CampagneController.php +++ b/app/Http/Controllers/CampagneController.php @@ -412,86 +412,4 @@ class CampagneController extends Controller 'Détails Campagne' => $data ]); } - - public function getDiffCamp(Request $request) - { - $error = false; - // ---- Contrôle des paramètres - if (isset($request->refcamp) && $request->refcamp != "") { - $refcamp = $request->refcamp; - $campagne = Campagne::where(['camp_id' => $refcamp])->first(); - if (!$campagne) { - return response()->json([ - 'Statut' => 'KO', - 'Code' => 103, - 'Message' => 'La campagne n\'existe pas' - ]); - } - } else { - $error = true; - } - if (isset($request->refsite) && $request->refsite != "") { - $refsite = $request->refsite; - $site = Site::where(['site_id' => $refsite])->first(); - if (!$site) { - return response()->json([ - 'Statut' => 'KO', - 'Code' => 102, - 'Message' => 'Le site n\'existe pas' - ]); - }; - } else { - $error = true; - } - - if (isset($request->refadherent) && $request->refadherent != "") { - $refadherent = $request->refadherent; - $contact = Contact::where(['contact_siteId' => $refsite, 'contact_ref' => $refadherent])->first(); - if (!$contact) { - return response()->json([ - 'Statut' => 'KO', - 'Code' => 104, - 'Message' => 'Le contact n\'existe pas' - ]); - } - } else { - $error = true; - } - - if ($error) { - return response()->json([ - 'Statut' => 'KO', - 'Code' => 101, - 'Message' => 'Des paramètres de la requête sont mal renseignés ou non présents' - ]); - } - - $data = array ( - 'Statut' => 'OK', - 'Site' => $site->site_nom, - 'Ref campagne' => $campagne->camp_id, - 'Nom campagne' => $campagne->camp_name, - "Ref contact" => $refadherent, - "Email contact" => $contact->contact_email, - ); - - $coupons = $campagne->coupons()->get(); - - if ($coupons->count() > 0) { - foreach ($coupons as &$coupon) { - if ($this->isAvailable($coupon, $campagne, $contact)) { - $dispo = 1; - } else { - $dispo = 0; - } - $codeTraitement = array ( - "Code traitement" => $coupon->coupon_code, - "Nom" => $coupon->coupon_name, - "Nb disponible" => $dispo, - ); - $data['Codes traitement'][] = $codeTraitement; - } - } - return response()->json($data); - } } diff --git a/app/Http/Controllers/ContactController.php b/app/Http/Controllers/ContactController.php index ac15edb..db55bf0 100644 --- a/app/Http/Controllers/ContactController.php +++ b/app/Http/Controllers/ContactController.php @@ -4,15 +4,6 @@ namespace App\Http\Controllers; use App\Contact; use App\Site; -use App\Campagne; -use App\Enums\CampaignTypes; -use App\Http\Requests\Contact\LoadWidgetContact; -use App\Http\Requests\Contact\SearchContact; -use App\Http\Requests\Contact\StoreContact; -use App\Http\Requests\Contact\UpdateContact; -use App\Log; -use App\Panier; -use App\PanierContent; use App\Traits\CouponTrait; use Illuminate\Http\Request; @@ -20,103 +11,6 @@ class ContactController extends Controller { use CouponTrait; - /** - * Display a listing of the resource. - * - * @return \Illuminate\Http\Response - */ - public function index(Request $request) - { - return view('contact.index')->with('search', false); - } - - - public function search(SearchContact $request) - { - session()->put('active_route', 'contact'); - - $contacts = Contact::where('contact_siteId', session()->get('site')->site_id) - ->searchFirstname($request->searchFirstname) - ->searchName($request->searchName) - ->searchEmail($request->searchEmail) - ->searchCp($request->searchCp) - ->searchVille($request->searchVille) - ->searchCountry($request->searchCountry) - ->orderBy('contact_createdate', 'DESC') - ->paginate(15, ['*'], 'page', $request->page); - - $searchData = array( - 'name' => $request->searchName, - 'firstname' => $request->searchFirstname, - 'email' => $request->searchEmail, - 'cp' => $request->searchCp, - 'ville' => $request->searchVille, - 'pays' => $request->searchCountry - ); - - return view('contact.index')->with('contacts', $contacts)->with('search', true)->with('searchData', $searchData); - } - - /** - * Show the form for creating a new resource. - * - * @return \Illuminate\Http\Response - */ - public function create() - { - return view('contact.create'); - } - - /** - * Store a newly created resource in storage. - * - * @param StoreContact $request - * @return \Illuminate\Http\Response - */ - public function store(StoreContact $request) - { - // TODO : Revoir le passage du site - $site = session()->get('site'); - - $contact = new Contact(); - $contact->fill($request->validated()); - $contact->contact_siteId = $site->site_id; - $contact->save(); - - return redirect()->route('contact.edit', ['contact' => $contact]); - } - - /** - * Show the form for editing the specified resource. - * - * @param \App\Campagne $campagne - * @return \Illuminate\Http\Response - */ - public function edit(Contact $contact) - { - $campagnes = Campagne::where(['camp_siteId' => $contact->contact_siteId, 'camp_published' => 1])->get(); - - return view('contact.edit', ['contact' => $contact, 'campagnes' => $campagnes]); - } - - /** - * Update the specified resource in storage. - * - * @param UpdateContact $request - * @param \App\Contact $contact - * @return \Illuminate\Http\Response - */ - public function update(UpdateContact $request, Contact $contact) - { - $contact->fill($request->validated()); - if ($contact->save()) { - session()->flash('update_ok', __('La campagne a bien été modifiée')); - } else { - session()->flash('update_nok', "Erreur lors de l'enregistrement"); - } - return redirect()->route('contact.edit', ['contact' => $contact]); - } - public function updateContact(Request $request) { $error = false; @@ -228,102 +122,4 @@ class ContactController extends Controller ]); } } - - public function loadWidget(LoadWidgetContact $request, Contact $contact) - { - $camp_id = $request->camp_id; - $campagne = Campagne::where(['camp_id' => $camp_id])->first(); - $coupons = $campagne->coupons; - - foreach ($coupons as $key => $coupon) { - if (!$this->isAvailableToPrint($coupon, $campagne)) { - $coupons->forget($key); - } - } - - $allLogs = Log::where(['log_refsite' => $campagne->camp_siteId, 'log_contactId' => $contact->contact_id, 'log_statut' => 'OK'])->orderBy('created_at', 'DESC')->get(); - $logs = array(); - foreach ($allLogs as &$l) { - $params = json_decode($l->log_params); - if ($l->log_action == 3 || $l->log_action == 1) { - $l->log_message = json_decode($l->log_message, true); - } - if (intval($params->refcamp) == $camp_id) { - array_push($logs, $l); - } - } - - // ---- Pour les types murs - if ($campagne->camp_type->equals(CampaignTypes::mur()) || $campagne->camp_type->equals(CampaignTypes::murss())) { - foreach ($coupons as &$coupon) { - $coupon->isAvailable = $this->isAvailable($coupon, $campagne, $contact); - } - unset($coupon); - - // Montant total et Nombre réductions disponible - $montantDispo = 0; - $montantTotal = 0; - $nbCoupon = 0; - foreach ($coupons as $coupon) { - $montantTotal += $coupon->coupon_value; - if ($coupon->isAvailable == true) { - $montantDispo += $coupon->coupon_value; - $nbCoupon++; - } - } - - if (count($coupons) === $nbCoupon) { - $reset = false; - } else { - $reset = true; - } - - $campagne->url = url('/') . "/?refsite=" . $campagne->camp_siteId . "&refcamp=" . $camp_id . "&hash=" . $contact->contact_hash; - - $returnHTML = view('widgets.mur') - ->with([ - 'campagne' => $campagne, - 'reset' => $reset, - 'montantTotal' => $montantTotal, - 'montantDispo' => $montantDispo, - 'logs' => $logs, - 'contact' => $contact]) - ->renderSections()['widget']; - - return json_encode(array('success' => true, 'html' => $returnHTML)); - } else { - // ---- Pour les types paniers - $panier = Panier::where(['panier_campId' => $campagne->camp_id, 'panier_contactId' => $contact->contact_id, 'panier_pending' => 1])->first(); - if ($panier) { - $panier_ref = $panier->panier_ref; - foreach ($coupons as &$coupon) { - $paniercontent = PanierContent::where(['panier_content_couponId' => $coupon->coupon_id, 'panier_content_panierId' => $panier->panier_id])->first(); - if ($paniercontent) { - $coupon->qty = $paniercontent->panier_content_quantity; - } else { - $coupon->qty = 0; - } - } - $campagne->url = url('/') . "/?basketid=" . $panier_ref; - unset($coupon); - } else { - $panier_ref = ""; - foreach ($coupons as &$coupon) { - $coupon->qty = 0; - } - $campagne->url = "Pas d'url disponible (panier non créé)"; - } - - $returnHTML = view('widgets.panier') - ->with([ - 'campagne' => $campagne, - 'contact' => $contact, - 'panier' => $panier_ref, - 'logs' => $logs, - 'coupons' => $coupons - ]) - ->renderSections()['widget']; - return json_encode(array('success' => true, 'html' => $returnHTML)); - } - } } diff --git a/app/Http/Controllers/CustomersController.php b/app/Http/Controllers/CustomersController.php deleted file mode 100644 index 21c1184..0000000 --- a/app/Http/Controllers/CustomersController.php +++ /dev/null @@ -1,33 +0,0 @@ -email; - - $site->load("campagnes"); - - if ($email) { - $customer = Customers::where('email', $email)->first(); - if ($customer) { - $publishedCampaign = $site->campagnes->where('camp_published', 1)->pluck('camp_id'); - - $campaigns = Campagne::where('camp_published', 1)->whereHas('printedCoupons', function ($q) use ($customer, $publishedCampaign) { - $q->where('customer_id', $customer->id)->whereIn('camp_id', $publishedCampaign); - })->get(); - } - } - - return view('customers.index', compact('site', 'customer', 'campaigns', 'email')); - } -} diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php deleted file mode 100644 index e2029da..0000000 --- a/app/Http/Controllers/ExportController.php +++ /dev/null @@ -1,86 +0,0 @@ -format('Y-m-d'); - $enddate = date('Y-m-d'); - - return view('export.index')->with(['sites' => $sites, 'startdate' => $startdate, 'enddate' => $enddate]); - } - - // ------ AJAX Export - public function exportCoupons(PDFExport $request) - { - $ids = $request->ids; - $startdate = $request->startdate; - $enddate = $request->enddate; - - $coupons = Coupon::find($ids); - - $labels = $this->getLabelsForStats($startdate, $enddate); - - $paths = array(); - - foreach ($coupons as $coupon) { - $cpName = str_replace(' ', '', $coupon->coupon_name); - $full_path = 'export/' . $cpName . '_' . date('d_m_Y') . '.pdf'; - $dataCoupon = $this->getDataForCouponAllCamp($labels, $coupon); - $coupon->couponQuantity = $this->getFullQuantity($coupon); - $coupon->couponFullPrinted = $this->getUsedQuantity($coupon); - $coupon->couponPrinted = $this->getUsedQuantityOnPeriod($coupon, $startdate, $enddate); - - $pdf = App::make('dompdf.wrapper'); - - $myData = new Data(); - - $myData->addPoints($dataCoupon, "My Serie 1"); - $myData->addPoints($labels, "My Serie 2"); - $myData->setAxisName(0, "Coupons Diffusés"); - $myData->setAbscissa("My Serie 2"); - $myData->setSerieTicks("My Serie 2", 2); - $serieSettings = array("R" => 229,"G" => 11,"B" => 11); - $myData->setPalette("My Serie 1", $serieSettings); - $myPicture = new Image(700, 230, $myData); - $myPicture->setGraphArea(60, 40, 670, 190); - $myPicture->setFontProperties(array("FontSize" => 10)); - $myPicture->drawScale(array("DrawSubTicks" => true, 'LabelSkip' => 1, 'LabelRotation' => 25, "Mode" => SCALE_MODE_START0, "CycleBackground" => true,"GridR" => 102,"GridG" => 102,"GridB" => 102)); - $myPicture->setShadow(true, array("X" => 1,"Y" => 1,"R" => 0,"G" => 0,"B" => 0,"Alpha" => 10)); - $myPicture->drawLineChart(array("DisplayColor" => DISPLAY_AUTO)); - $myPicture->Render('export/temp.png'); - - $view = view('export.template')->with(['src' => 'export/temp.png', 'coupon' => $coupon, 'startdate' => $startdate, 'enddate' => $enddate])->render(); - - $pdf->loadHTML($view); - $pdf->save($full_path); - array_push($paths, $full_path); - }//end foreach - - return json_encode($paths); - } -} diff --git a/app/Http/Controllers/MaintenanceController.php b/app/Http/Controllers/MaintenanceController.php deleted file mode 100644 index 6a76194..0000000 --- a/app/Http/Controllers/MaintenanceController.php +++ /dev/null @@ -1,88 +0,0 @@ - 'NOK'])->whereMonth('created_at', $month)->count(); - array_push($count, [$month, $logs]); - } - - return view('maintenance.index')->with(['users' => $users, 'logs' => $count]); - } - - public function usercreate(Request $request) - { - $sites = Site::all(); - return view('user.create')->with(['sites' => $sites]); - } - - public function userstore(StoreUsers $request) - { - $user = new User(); - $user->fill($request->validated()); - $user->password = Hash::make($request->password); - $user->save(); - - $sites = Site::whereIn('site_id', $request->site)->get()->pluck('id')->toArray(); - $user->sites()->sync($sites); - - return redirect()->route('maintenance.user.edit', ['user' => $user]); - } - - public function useredit(User $user) - { - $sites = Site::all(); - $usersites = $user->sites()->get()->pluck('site_id')->toArray(); - return view('user.edit')->with(['user' => $user, 'sites' => $sites, 'usersites' => $usersites]); - } - - public function userupdate(UpdateUsers $request, User $user) - { - //MAJ du User - $user->fill($request->validated()); - $user->save(); - - //MAJ des sites liés - $sites = Site::whereIn('site_id', $request->site)->get()->pluck('id')->toArray(); - $user->sites()->sync($sites); - - $marques = Auth::user()->sites; - $submarques = $marques->map(function ($marque) { - return $marque->only(['id', 'site_nom', 'site_id', 'site_logo']); - }); - - session()->put('marques', $submarques); - return redirect()->route('maintenance.show'); - } - - public function userdestroy(User $user) - { - if ($user->delete()) { - return redirect()->route('maintenance.show'); - } else { - session()->flash('delete_nok', 'Erreur lors de la suppression'); - return view('maintenance.user.edit')->with('user', $user); - } - } -} diff --git a/app/Http/Controllers/MurController.php b/app/Http/Controllers/MurController.php deleted file mode 100644 index 1c6283a..0000000 --- a/app/Http/Controllers/MurController.php +++ /dev/null @@ -1,315 +0,0 @@ -all(); - if (isset($datas['refcamp'])) { - $ref = intval($datas['refcamp']); - $campagne = Campagne::where(['camp_id' => $ref])->first(); - } else { - abort(404); - } - if (!isset($datas['refsite'])) { - abort(404); - } - if (!isset($campagne) || $campagne->camp_published == 0 || ($campagne->camp_type->value != 0 && $campagne->camp_type->value != 2)) { - abort(404); - } - - if (isset($datas['hash'])) { - $contact = Contact::where([ - 'contact_hash' => $datas['hash'], 'contact_siteId' => $campagne->camp_siteId - ])->first(); - } else { - abort(404); - } - - $coupons = $this->getCouponsPublished($campagne); - - foreach ($coupons as $coupon) { - $coupon->isAvailable = $this->isAvailable($coupon, $campagne, $contact); - $coupon->isAvailableToPrint = $this->isAvailableToPrint($coupon, $campagne); - } - unset($coupon); - - // Montant total et Nombre réductions disponible - $montantDispo = 0; - $montantTotal = 0; - $nbCoupon = 0; - $nbCouponToPrint = 0; - foreach ($coupons as $coupon) { - $montantTotal += $coupon->coupon_value; - if ($coupon->isAvailable == true) { - $montantDispo += $coupon->coupon_value; - $nbCoupon++; - } - if ($coupon->isAvailableToPrint == true) { - $nbCouponToPrint++; - } - } - - session()->put('campagne', $campagne); - session()->put('site', $campagne->site); - session()->put('contact', $contact); - - return view('landings::' . $ref . '.index') - ->with(['ref' => $ref, 'coupons' => $coupons, 'contact' => $contact, 'nb' => $nbCoupon, 'nbAvailableToPrint' => $nbCouponToPrint, 'montantDispo' => $montantDispo]); - } - - public function printMur(Request $request) - { - if (isset($request->coupons)) { - session()->put('coupons', $request->coupons); - $coupons = $request->coupons; - } - - if ($request->session()->has('campagne')) { - $campagne = $request->session()->get('campagne'); - } else { - abort(419); - } - - $ref = $campagne->camp_id; - - return view('landings::' . $ref . '.imprimer') - ->with(['ref' => $ref, 'coupons' => $coupons, 'contact' => $request->session()->get('contact')]); - } - - public function deleteControlContact(Request $request) - { - if (isset($request->admin)) { - $source = 'ADMIN'; - } else { - $source = 'API'; - } - - // ---- INIT du log - $log = new Log(); - $log->log_action = config('constants.delctrlcontact'); - $log->log_source = $source; - - $error = false; - // ---- Contrôle des paramètres - if (isset($request->refcamp) && $request->refcamp != "") { - $refcamp = $request->refcamp; - } else { - $error = true; - } - if (isset($request->refadherent) && $request->refadherent != "") { - $refadherent = $request->refadherent; - } else { - $error = true; - } - // Erreur si paramètres mal renseignés - if ($error) { - $log->log_params = json_encode(['refcamp' => $request->refcamp, 'refadherent' => $request->refadherent]); - $log->log_statut = 'NOK'; - $log->log_message = "Des paramètres de la requête sont mal renseignés ou non présents"; - $log->save(); - - return response()->json([ - 'Statut' => 'KO', - 'Code' => 101, - 'Message' => 'Des paramètres de la requête sont mal renseignés ou non présents' - ]); - } - - // Check de la campagne - $campagne = Campagne::where('camp_id', $refcamp)->first(); - - if (!$campagne) { - $log->log_params = json_encode(['refcamp' => $request->refcamp, 'refadherent' => $request->refadherent]); - $log->log_statut = 'NOK'; - $log->log_message = "La campagne n'existe pas"; - $log->save(); - return response()->json([ - 'Statut' => 'KO', - 'Code' => 103, - 'Message' => 'La campagne n\'existe pas' - ]); - } - - - // Check du contact - $contact = Contact::where(['contact_ref' => $refadherent, 'contact_siteId' => $campagne->camp_siteId])->first(); - - if (!$contact) { - $log->log_params = json_encode(['refcamp' => $request->refcamp, 'refadherent' => $request->refadherent]); - $log->log_refsite = $campagne->camp_siteId; - $log->log_statut = 'NOK'; - $log->log_message = "Le contact n'existe pas"; - $log->save(); - - return response()->json([ - 'Statut' => 'KO', - 'Code' => 104, - 'Message' => 'Le contact n\'existe pas' - ]); - } - - if ($campagne) { - // Update - $mur = Mur::where(['mur_campId' => $refcamp, 'mur_contactId' => $contact->contact_id])->update(['mur_deleted' => '1']); - if ($mur != 0) { - $log->log_params = json_encode(['refcamp' => $request->refcamp, 'refadherent' => $request->refadherent]); - $log->log_refsite = $campagne->camp_siteId; - $log->log_contactId = $contact->contact_id; - $log->log_statut = 'OK'; - $log->log_message = $mur . " enregistrements ont été mis à jour."; - $log->save(); - return response()->json([ - 'Statut' => 'OK', - 'Message' => $mur . " enregistrements ont été mis à jour." - ]); - } else { - $log->log_params = json_encode(['refcamp' => $request->refcamp, 'refadherent' => $request->refadherent]); - $log->log_refsite = $campagne->camp_siteId; - $log->log_contactId = $contact->contact_id; - $log->log_statut = 'OK'; - $log->log_message = 'Aucun contrôle à effacer'; - $log->save(); - return response()->json([ - 'Statut' => 'KO', - 'Code' => 110, - 'Message' => 'Aucun contrôle à effacer' - ]); - }//end if - }//end if - return; - } - - public function montantDispoCampagne(Request $request) - { - - // ---- Contrôle des paramètres - if (isset($request->refcamp) && $request->refcamp != "") { - $refcamp = $request->refcamp; - } else { - return response()->json([ - 'Statut' => 'KO', - 'Code' => 101, - 'Message' => 'Des paramètres de la requête sont mal renseignés ou non présents' - ]); - } - - // ------ Recuperation de la campagne - $camp = Campagne::where(['camp_id' => $refcamp])->first(); - if (!$camp) { - return response()->json([ - 'Statut' => 'KO', - 'Code' => 103, - 'Message' => 'La campagne n\'existe pas' - ]); - } - - // ------ Récupération des coupons de la campagne - $coupons = $camp->coupons()->where(['coupon_published' => 1])->get(); - if (count($coupons) == 0) { - return response()->json([ - 'Statut' => 'KO', - 'Code' => 105, - 'Message' => 'La campagne est vide' - ]); - } - - // ---- Montant total et Nombre réductions disponible - $montantTotal = 0; - foreach ($coupons as $coupon) { - $montantTotal += $coupon->coupon_value; - } - - $montantTotal = number_format($montantTotal, 2, ",", " "); - $montantTotal = str_pad($montantTotal, 6, "0", STR_PAD_LEFT); - - return response()->json([ - 'Statut' => 'OK', - 'Message' => $montantTotal - ]); - } - - public function montantDispoContact(Request $request) - { - $error = false; - if (isset($request->refcamp) && $request->refcamp != "") { - $refcamp = $request->refcamp; - } else { - $error = true; - } - - if (isset($request->refsite) && $request->refsite != "") { - $refsite = $request->refsite; - } else { - $error = true; - } - - if (isset($request->refadherent) && $request->refadherent != "") { - $refadherent = $request->refadherent; - } else { - $error = true; - } - - if ($error) { - return response()->json([ - 'Statut' => 'KO', - 'Code' => 101, - 'Message' => 'Des paramètres de la requête sont mal renseignés ou non présents' - ]); - } - - $campagne = Campagne::where(['camp_id' => $refcamp])->where('camp_type', 'IN', array(0,2))->first(); - - if (!$campagne) { - return response()->json([ - 'Statut' => 'KO', - 'Code' => 103, - 'Message' => 'La campagne n\'existe pas' - ]); - } - - $contact = Contact::where(['contact_siteId' => $refsite, 'contact_ref' => $refadherent])->first(); - if (!$contact) { - return response()->json([ - 'Statut' => 'KO', - 'Code' => 104, - 'Message' => 'Le contact n\'existe pas' - ]); - } - - $coupons = $this->getCouponsPublished($campagne); - - foreach ($coupons as &$coupon) { - $coupon->isAvailable = $this->isAvailable($coupon, $campagne, $contact); - } - unset($coupon); - - $montantDispo = 0; - foreach ($coupons as $coupon) { - if ($coupon->isAvailable == true) { - $montantDispo += $coupon->coupon_value; - } - } - - $montantDispo = number_format($montantDispo, 2, ",", " "); - $montantDispo = str_pad($montantDispo, 6, "0", STR_PAD_LEFT); - - return response()->json([ - 'Statut' => 'OK', - 'Message' => $montantDispo - ]); - } -} diff --git a/app/Http/Controllers/RedirectController.php b/app/Http/Controllers/RedirectController.php index 8ec9049..289642f 100644 --- a/app/Http/Controllers/RedirectController.php +++ b/app/Http/Controllers/RedirectController.php @@ -15,16 +15,12 @@ class RedirectController extends Controller */ public function __invoke(Request $request) { - $mur = new Controllers\MurController(); $panier = new Controllers\PanierController(); if (isset($request->basketid)) { return $panier->showTemplate($request); -// $this->showTemplate($request); - } elseif (isset($request->hash)) { - return $mur->show($request); } else { - return redirect()->route('home'); + return redirect()->route('login'); } } } diff --git a/app/Http/Controllers/StatistiqueController.php b/app/Http/Controllers/StatistiqueController.php deleted file mode 100644 index 3227ff2..0000000 --- a/app/Http/Controllers/StatistiqueController.php +++ /dev/null @@ -1,93 +0,0 @@ -sites()->get(); - // foreach ($sites as $site) { - // if (isset($site->campagnes)) { - // $campagnes = $site->campagnes; - // foreach ($campagnes as $campagne) { - // $coupons = $campagne->coupons; - // foreach ($coupons as $coupon) { - // $coupon->quantity = $this->getFullQuantity($coupon); - // $coupon->remain = $this->getFullQuantity($coupon) - $this->getUsedQuantity($coupon); - // $coupon->diffused = $this->getDiffusedQuantity($coupon); - // $coupon->isPrintable = $this->isAvailableToPrint($coupon, $campagne); - // $coupon->isPaused = $this->isPausedToPrint($coupon, $campagne); - // } - // }//end foreach - // }//end if - // }//end foreach - $sites = []; - return view('stats.index')->with(['sites' => $sites]); - } - - /** - * Show the stat detail of a campaign. - * - * @return \Illuminate\Http\Response - */ - public function statdetail(Campagne $campagne) - { - $count = 0; - $colors = config('constants.colors'); - - $coupons = $campagne->coupons; - foreach ($coupons as $coupon) { - $coupon->quantity = $this->getFullQuantity($coupon); - $coupon->remain = $coupon->quantity - $this->getUsedQuantity($coupon); - $coupon->diffused = $this->getDiffusedQuantity($coupon); - $coupon->isPrintable = $this->isAvailableToPrint($coupon, $campagne); - $coupon->isPaused = $this->isPausedToPrint($coupon, $campagne); - $coupon->color = $coupon->color = "#" . str_pad(dechex(mt_rand(0, 255)), 2, '0', STR_PAD_LEFT) . str_pad(dechex(mt_rand(0, 255)), 2, '0', STR_PAD_LEFT) . str_pad(dechex(mt_rand(0, 255)), 2, '0', STR_PAD_LEFT); - $count++; - } - $enddate = date('Y-m-d'); - $startdate = date('Y-m-d', strtotime("-30 days")); - return view('stats.detail')->with(['campagne' => $campagne, 'startdate' => $startdate, 'enddate' => $enddate]); - } - - public function loadStats(Request $request) - { - $campid = $request->campid; - $startdate = $request->startdate; - $enddate = $request->enddate; - - // ---- Labels pour la période - $labels = $this->getLabelsForStats($startdate, $enddate); - - // ---- Data pour le graphique global - $dataGlobal = $this->getDataForCamp($labels, $campid); - - // ---- Data pour le graphique par coupons - $campagne = Campagne::where(['camp_id' => $campid])->first(); - $coupons = $campagne->coupons; - $dataSpec = []; - foreach ($coupons as $coupon) { - $dataCoupon = $this->getDataForCoupon($labels, $campid, $coupon); - $dataSpec[$coupon->coupon_code] = $dataCoupon; - } - - $data = array('labels' => $labels, 'dataGlobal' => $dataGlobal, 'dataSpec' => $dataSpec); - - return json_encode($data); - } -} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index d4b0c2d..9742dae 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -60,11 +60,7 @@ class Kernel extends HttpKernel 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, - 'process.url' => \App\Http\Middleware\ProcessSpecificUrl::class, 'saveroute' => \App\Http\Middleware\SaveCurrentRoute::class, - 'superadmin' => \App\Http\Middleware\SuperAdmin::class, - 'admin' => \App\Http\Middleware\Admin::class, - 'marketing' => \App\Http\Middleware\Marketing::class, ]; /** diff --git a/app/Http/Middleware/Admin.php b/app/Http/Middleware/Admin.php deleted file mode 100644 index 67d123d..0000000 --- a/app/Http/Middleware/Admin.php +++ /dev/null @@ -1,27 +0,0 @@ -roles == 'admin') { - return $next($request); - } elseif (Auth::check() && Auth::user()->roles == 'superadmin') { - return $next($request); - } else { - return redirect('/bo/home'); - } - } -} diff --git a/app/Http/Middleware/Marketing.php b/app/Http/Middleware/Marketing.php deleted file mode 100644 index 90140a1..0000000 --- a/app/Http/Middleware/Marketing.php +++ /dev/null @@ -1,20 +0,0 @@ -path(); - $params = $request->all(); - $paramsCount = count($params); - $contact = new Controllers\ContactController(); - $mur = new Controllers\MurController(); - $panier = new Controllers\PanierController(); - $campagne = new Controllers\CampagneController(); - $ajax = new Controllers\AjaxController(); - switch ($path) { - case 'updatecontact': - $res = $contact->updateContact($request); - break; - case 'delctrlcontact': - $res = $mur->deleteControlContact($request); - break; - case 'mntdispocampagne': - $res = $mur->montantDispoCampagne($request); - break; - case 'mntdispocontact': - $res = $mur->montantDispoContact($request); - break; - case 'updatebasket': - $res = $panier->updateBasket($request); - break; - case 'showbasket': - $res = $panier->showBasket($request); - break; - case 'getdetailcampaign': - $res = $campagne->getDetail($request); - break; - case 'getdiffctccampaign': - $res = $campagne->getDiffCamp($request); - break; - case 'imprime': - $res = $panier->printPanierSelect($request); - return $next($request); - break; - case 'imprimer': - $res = $mur->printMur($request); - return $next($request); - break; - case 'merci': - $res = $mur->getThanks($request); - return $next($request); - break; - case 'merci-panier': - $res = $panier->getThanks($request); - return $next($request); - break; - case 'index': - $res = $ajax->backToIndex($request); - break; - default: - switch ($paramsCount) { - case 0: - return redirect('bo/home'); - break; - case 1: - $res = $panier->showTemplate($request); - return $next($request); - break; - case 3: - $res = $mur->show($request); - return $next($request); - break; - case 4: - $res = $mur->show($request); - return $next($request); - break; - default: - break; - } - break; - }//end switch - - return $res; - } -} diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php index 69d750d..bc9bec4 100644 --- a/app/Http/Middleware/RedirectIfAuthenticated.php +++ b/app/Http/Middleware/RedirectIfAuthenticated.php @@ -18,7 +18,7 @@ class RedirectIfAuthenticated public function handle($request, Closure $next, $guard = null) { if (Auth::guard($guard)->check()) { - return redirect()->route('home'); + return redirect()->route('campagne.index'); } return $next($request); diff --git a/app/Http/Middleware/SaveCurrentRoute.php b/app/Http/Middleware/SaveCurrentRoute.php index 45bdab3..f1a64f3 100644 --- a/app/Http/Middleware/SaveCurrentRoute.php +++ b/app/Http/Middleware/SaveCurrentRoute.php @@ -2,8 +2,8 @@ namespace App\Http\Middleware; -use Auth; use Closure; +use Illuminate\Support\Facades\Auth; class SaveCurrentRoute { @@ -16,26 +16,22 @@ class SaveCurrentRoute */ public function handle($request, Closure $next) { - $routeName = explode('.', \Route::currentRouteName()); - - session()->put('active_route', $routeName[0]); - $site = session()->get('site'); - - if (!isset($site)) { - session()->put('site', Auth::user()->sites->first()); - } - - $currentLogo = session()->get('site')->only(['site_logo']); - session()->put('current_logo', $currentLogo); $siteId = session()->get('marques'); - if (!isset($siteId)) { - $marques = Auth::user()->sites; + if (!$site) { + $site = Auth::user()->sites->first(); + session()->put('site', $site); + } + + $currentLogo = $site->only(['site_logo']); + session()->put('current_logo', $currentLogo); + + if (!$siteId) { + $marques = Auth::user()->sites; $submarques = $marques->map(function ($marque) { return $marque->only(['id', 'site_nom', 'site_id', 'site_logo']); }); - session()->put('marques', $submarques); } diff --git a/app/Http/Middleware/SuperAdmin.php b/app/Http/Middleware/SuperAdmin.php deleted file mode 100644 index 2b89f20..0000000 --- a/app/Http/Middleware/SuperAdmin.php +++ /dev/null @@ -1,25 +0,0 @@ -roles == 'superadmin') { - return $next($request); - } else { - return redirect('/bo/home'); - } - } -} diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 44c4e6b..533a515 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -22,6 +22,5 @@ class VerifyCsrfToken extends Middleware 'updatecontact', 'updatebasket', 'showbasket', - 'delctrlcontact' ]; } diff --git a/app/Http/Requests/Contact/LoadWidgetContact.php b/app/Http/Requests/Bo/Users/DestroyUserRequest.php similarity index 57% rename from app/Http/Requests/Contact/LoadWidgetContact.php rename to app/Http/Requests/Bo/Users/DestroyUserRequest.php index 78dfea1..40d6597 100644 --- a/app/Http/Requests/Contact/LoadWidgetContact.php +++ b/app/Http/Requests/Bo/Users/DestroyUserRequest.php @@ -1,12 +1,11 @@ roles === UserRoles::superadmin()->value || Auth::user()->roles === UserRoles::admin()->value); + return Auth::check(); } /** @@ -26,7 +25,7 @@ class LoadWidgetContact extends FormRequest public function rules() { return [ - 'camp_id' => 'required|exists:campagne,camp_id' + // ]; } } diff --git a/app/Http/Requests/Users/StoreUsers.php b/app/Http/Requests/Bo/Users/StoreUserRequest.php similarity index 72% rename from app/Http/Requests/Users/StoreUsers.php rename to app/Http/Requests/Bo/Users/StoreUserRequest.php index e0f7cb9..a959669 100644 --- a/app/Http/Requests/Users/StoreUsers.php +++ b/app/Http/Requests/Bo/Users/StoreUserRequest.php @@ -1,12 +1,11 @@ roles === UserRoles::superadmin()->value || Auth::user()->roles === UserRoles::admin()->value); + return Auth::check(); } /** diff --git a/app/Http/Requests/Users/UpdateUsers.php b/app/Http/Requests/Bo/Users/UpdateUserRequest.php similarity index 73% rename from app/Http/Requests/Users/UpdateUsers.php rename to app/Http/Requests/Bo/Users/UpdateUserRequest.php index eaf7b34..6bc8570 100644 --- a/app/Http/Requests/Users/UpdateUsers.php +++ b/app/Http/Requests/Bo/Users/UpdateUserRequest.php @@ -1,13 +1,12 @@ roles === UserRoles::superadmin()->value || Auth::user()->roles === UserRoles::admin()->value); + return Auth::check(); } /** diff --git a/app/Http/Requests/Contact/SearchContact.php b/app/Http/Requests/Contact/SearchContact.php deleted file mode 100644 index 33f0c14..0000000 --- a/app/Http/Requests/Contact/SearchContact.php +++ /dev/null @@ -1,38 +0,0 @@ -roles === UserRoles::superadmin()->value || Auth::user()->roles === UserRoles::admin()->value); - } - - /** - * Get the validation rules that apply to the request. - * - * @return array - */ - public function rules() - { - return [ - 'page' => 'sometimes|integer|nullable', - 'searchName' => 'sometimes|alpha|max:50|nullable', - 'searchFirstname' => 'sometimes|alpha|max:50|nullable', - 'searchEmail' => 'sometimes|email|max:100|nullable', - 'searchCp' => 'sometimes|integer|max:10|nullable', - 'searchVille' => 'sometimes|alpha|max:50|nullable', - 'searchCountry' => 'sometimes|integer|nullable', - ]; - } -} diff --git a/app/Http/Requests/Contact/StoreContact.php b/app/Http/Requests/Contact/StoreContact.php deleted file mode 100644 index d13e5ff..0000000 --- a/app/Http/Requests/Contact/StoreContact.php +++ /dev/null @@ -1,38 +0,0 @@ -roles === UserRoles::superadmin()->value || Auth::user()->roles === UserRoles::admin()->value); - } - - /** - * Get the validation rules that apply to the request. - * - * @return array - */ - public function rules() - { - return [ - 'contact_ref' => 'required|unique:contact', - 'contact_nom' => 'min:3|max:50|required', - 'contact_prenom' => 'min:3|max:50|required', - 'contact_email' => 'email|required', - 'contact_cp' => 'max:10|required', - 'contact_ville' => 'max:50|required', - 'contact_country' => 'integer|required' - ]; - } -} diff --git a/app/Http/Requests/Contact/UpdateContact.php b/app/Http/Requests/Contact/UpdateContact.php deleted file mode 100644 index 8665ed5..0000000 --- a/app/Http/Requests/Contact/UpdateContact.php +++ /dev/null @@ -1,42 +0,0 @@ -roles === UserRoles::superadmin()->value || Auth::user()->roles === UserRoles::admin()->value); - } - - /** - * Get the validation rules that apply to the request. - * - * @return array - */ - public function rules() - { - return [ - 'contact_ref' => [ - 'required', - Rule::unique('contact')->ignore($this->contact->contact_id, 'contact_id'), - ], - 'contact_nom' => 'min:3|max:50|required', - 'contact_prenom' => 'min:3|max:50|required', - 'contact_email' => 'email|required', - 'contact_cp' => 'max:10|required', - 'contact_ville' => 'max:50|required', - 'contact_country' => 'integer|required' - ]; - } -} diff --git a/app/Traits/GraphTrait.php b/app/Traits/GraphTrait.php deleted file mode 100644 index a10dcd6..0000000 --- a/app/Traits/GraphTrait.php +++ /dev/null @@ -1,76 +0,0 @@ -diff($datetime2); - $days = $interval->format('%a'); - - // ---- Labels des graphiques - $labels = []; - for ($i = 0; $i <= $days; $i++) { - $date = date('d/m/Y', strtotime("+" . $i . " days", strtotime($startdate))); - array_push($labels, $date); - } - - return $labels; - } - - // ---- Data for global graph - public function getDataForCamp($labels, $campid) - { - $dataGlobal = []; - foreach ($labels as $label) { - $date = date('Y-m-d', strtotime(str_replace('/', '-', $label))); - $stat = Statistique::where(['stat_campId' => $campid])->whereDate('created_at', '=', $date)->sum('stat_couponPrinted'); - if (isset($stat)) { - $dataGlobal[] = $stat; - } else { - $dataGlobal[] = 0; - } - } - return $dataGlobal; - } - - // ---- Data for one coupon on one camp - public function getDataForCoupon($labels, $campid, $coupon) - { - $dataCoupon = []; - foreach ($labels as $label) { - $date = date('Y-m-d', strtotime(str_replace('/', '-', $label))); - $stat = Statistique::where(['stat_campId' => $campid, 'stat_couponId' => $coupon->coupon_id])->whereDate('created_at', '=', $date)->sum('stat_couponPrinted'); - if (isset($stat)) { - $dataCoupon[] = $stat; - } else { - $dataCoupon[] = 0; - } - } - return $dataCoupon; - } - - // ---- Data for one coupon all camp - public function getDataForCouponAllCamp($labels, $coupon) - { - $dataCoupon = []; - foreach ($labels as $label) { - $date = date('Y-m-d', strtotime(str_replace('/', '-', $label))); - $stat = Statistique::where(['stat_couponId' => $coupon->coupon_id])->whereDate('created_at', '=', $date)->sum('stat_couponPrinted'); - if (isset($stat)) { - $dataCoupon[] = $stat; - } else { - $dataCoupon[] = 0; - } - } - return $dataCoupon; - } -} diff --git a/composer.json b/composer.json index 751bea0..550f552 100644 --- a/composer.json +++ b/composer.json @@ -1,11 +1,6 @@ { - "name": "laravel/laravel", - "description": "The Laravel Framework.", - "keywords": [ - "framework", - "laravel" - ], - "license": "MIT", + "name": "uwil/mesbr", + "description": "Webcoupon management system", "type": "project", "require": { "php": "^7.3", @@ -22,7 +17,6 @@ "ldc/ldccrmv2client": "^3.0.5", "picqer/php-barcode-generator": "^0.3.0", "sentry/sentry-laravel": "^4.13", - "szymach/c-pchart": "^3.0", "uxweb/sweet-alert": "^2.0", "vladimir-yuldashev/laravel-queue-rabbitmq": "^11.3" }, diff --git a/composer.lock b/composer.lock index 762b60c..c5a61e5 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "30db3ec95147621d8486d7ad5796a124", + "content-hash": "1b3d7e201bf5b5d95441c22f9c4d2265", "packages": [ { "name": "barryvdh/laravel-dompdf", @@ -6572,77 +6572,6 @@ ], "time": "2024-11-08T15:21:10+00:00" }, - { - "name": "szymach/c-pchart", - "version": "v3.0.17", - "source": { - "type": "git", - "url": "https://github.com/szymach/c-pchart.git", - "reference": "c022568da78f73bb7acf7db7a974934a434d47d7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/szymach/c-pchart/zipball/c022568da78f73bb7acf7db7a974934a434d47d7", - "reference": "c022568da78f73bb7acf7db7a974934a434d47d7", - "shasum": "" - }, - "require": { - "ext-gd": "*", - "php": "^5.4|^7.0|^8.0" - }, - "require-dev": { - "codeception/codeception": "^4.1.22", - "codeception/module-asserts": "^1.3", - "codeception/module-filesystem": "^1.0", - "phpunit/phpunit": "^5.7|^9.5", - "squizlabs/php_codesniffer": "^3.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "2.0": "2.0.x-dev", - "dev-master": "3.0.x-dev" - } - }, - "autoload": { - "files": [ - "constants.php" - ], - "psr-4": { - "CpChart\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-3.0-only" - ], - "authors": [ - { - "name": "Jean-Damien Pogolotti", - "homepage": "http://www.pchart.net", - "role": "Creator of the original pChart library" - }, - { - "name": "Piotr Szymaszek", - "homepage": "https://github.com/szymach", - "role": "Developer of the CpChart wrapper package" - } - ], - "description": "Port of \"pChart\" library into PHP 5+", - "homepage": "https://github.com/szymach/c-pchart", - "keywords": [ - "CpChart", - "c-pChart", - "charts", - "pchart", - "statistics" - ], - "support": { - "issues": "https://github.com/szymach/c-pchart/issues", - "source": "https://github.com/szymach/c-pchart/tree/v3.0.17" - }, - "time": "2023-05-27T11:12:02+00:00" - }, { "name": "tijsverkoyen/css-to-inline-styles", "version": "v2.2.7", diff --git a/config/constants.php b/config/constants.php index 16a85d2..07e18c0 100644 --- a/config/constants.php +++ b/config/constants.php @@ -2,7 +2,6 @@ return [ 'updatebasket' => 1, -'delctrlcontact' => 2, 'impression' => 3, 'updatecontact' => 4, 'colors' => ['#ffe258', '#8c86ff', '#86ffbc', '#ffc286', '#86caff', '#ff86e7', '#ff869d', '#b9ff86', '#e7ff86', '#b986ff', '#ffae86', '#5c60ff', '#ff5c5c', '#3ba33b', '#8c86ff', '#86ffbc', '#ffc286', '#86caff', '#ff86e7', '#ff869d', '#b9ff86', '#e7ff86', '#b986ff', '#ffae86', '#5c60ff', '#ff5c5c', '#3ba33b'] diff --git a/resources/js/app.js b/resources/js/app.js index fa3e437..1549c0b 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -306,17 +306,3 @@ $('body').on('click', '.expand', function(event){ contentDiv.slideUp(); } }) - -$('.open-all-stats').on('click', function(event){ - event.preventDefault(); - $('.couponStats .content').each(function(){ - $(this).slideDown(500); - }) -}) - -$('.close-all-stats').on('click', function(event){ - event.preventDefault(); - $('.couponStats .content').each(function(){ - $(this).slideUp(500); - }) -}) \ No newline at end of file diff --git a/resources/views/bo/maintenance/index.blade.php b/resources/views/bo/maintenance/index.blade.php new file mode 100644 index 0000000..04a859b --- /dev/null +++ b/resources/views/bo/maintenance/index.blade.php @@ -0,0 +1,54 @@ +@extends('layouts.main') + +@section('content') + +

+ + {{ __('Maintenance') }} +

+
+
{{ __("Erreur d'appels") }} :
+ +
+@endsection + +@section('scripts') + + +@endsection diff --git a/resources/views/user/create.blade.php b/resources/views/bo/users/create.blade.php similarity index 95% rename from resources/views/user/create.blade.php rename to resources/views/bo/users/create.blade.php index 5b1e8da..3e47586 100644 --- a/resources/views/user/create.blade.php +++ b/resources/views/bo/users/create.blade.php @@ -15,7 +15,7 @@ @endif
-
+ @csrf
diff --git a/resources/views/user/edit.blade.php b/resources/views/bo/users/edit.blade.php similarity index 95% rename from resources/views/user/edit.blade.php rename to resources/views/bo/users/edit.blade.php index 15824cf..2646848 100644 --- a/resources/views/user/edit.blade.php +++ b/resources/views/bo/users/edit.blade.php @@ -15,7 +15,7 @@
@endif
- + @csrf @method('PUT')
diff --git a/resources/views/bo/users/index.blade.php b/resources/views/bo/users/index.blade.php new file mode 100644 index 0000000..013fbdf --- /dev/null +++ b/resources/views/bo/users/index.blade.php @@ -0,0 +1,51 @@ +@extends('layouts.main') + +@section('content') + +

+ + {{ __('Users') }} +

+ +
+
+
{{ __('Gestion des utilisateurs') }}
+ {{ __('Ajouter') }} +
+ + @if (isset($users) && count($users) > 0) + + + + + + + + + @foreach ($users as $user) + + + + + + @if (Auth::user()->id === $user->id) + + @else + + @endif + + @endforeach +
{{ __('Nom') }}{{ __('Email') }}{{ __('Rôle') }}{{ __('Site liés') }}
{{ $user->name }}{{ $user->email }}{{ $user->roles }} + @foreach ($user->sites as $site) +

{{ $site->site_nom }}

+ @endforeach +
+ + @csrf + @method('DELETE') + + +
+ @endif +
+@endsection \ No newline at end of file diff --git a/resources/views/contact/create.blade.php b/resources/views/contact/create.blade.php deleted file mode 100644 index ac7fbaf..0000000 --- a/resources/views/contact/create.blade.php +++ /dev/null @@ -1,66 +0,0 @@ -@extends('layouts.main') - -@section('content') -

- - {{ __('Créer un contact') }} -

-@if ($errors->any()) -
-
    - @foreach ($errors->all() as $error) -
  • {{ $error }}
  • - @endforeach -
-
-@endif -
-
- @csrf -
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - -
-
-
- {{ __('Les champs avec un * sont obligatoires') }} - -
-
-
-@endsection diff --git a/resources/views/contact/edit.blade.php b/resources/views/contact/edit.blade.php deleted file mode 100644 index fb7aea0..0000000 --- a/resources/views/contact/edit.blade.php +++ /dev/null @@ -1,281 +0,0 @@ -@extends('layouts.main') - -@section('content') -

- - {{ __('Editer le contact') }} -

-@if ($errors->any()) -
-
    - @foreach ($errors->all() as $error) -
  • {{ $error }}
  • - @endforeach -
-
-@endif -
-
- @csrf - @method('PUT') -
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - -
-
- -
- {{ __('Les champs avec un * sont obligatoires') }} - -
-
-
-

- - - - {{ __('Campagnes liées') }} -

-
- - - @foreach ($campagnes as $campagne) -
-

{{$campagne->camp_name}}

-

{{ __('Type') }} : {{ $campagne->camp_type->label }}

- -
-
-
-
-
-

{{ __('Chargement') }}...

-
- -
- @endforeach -
-@endsection -@section('scripts') - -@endsection diff --git a/resources/views/contact/index.blade.php b/resources/views/contact/index.blade.php deleted file mode 100644 index 9755fa8..0000000 --- a/resources/views/contact/index.blade.php +++ /dev/null @@ -1,111 +0,0 @@ -@extends('layouts.main') - -@section('content') - -

- - {{ __('Recherche de contacts') }} - - - {{ __('Ajouter') }} - -

-
- @if ($errors->any()) -
-
    - @foreach ($errors->all() as $error) -
  • {{ $error }}
  • - @endforeach -
-
- @endif -
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
- -
-
-
-
-@if(isset($contacts) && count($contacts)>0) -
- - - - - - - - - - @foreach ($contacts as $contact) - @php - if (!in_array($contact->contact_country, Countries::toValues())) { - $contact->contact_country = 250; - } - @endphp - - - - - - - - - @endforeach - - - - - -
{{ __('Ref') }}{{ __('Nom') }}{{ __('Email') }}{{ __('Code Postal') }}{{ __('Ville') }}{{ __('Pays') }}
{{ $contact->contact_ref }}{{ $contact->contact_prenom }} {{$contact->contact_nom}}{{ $contact->contact_email }}{{ $contact->contact_cp }}{{ $contact->contact_ville }}{{ Countries::make($contact->contact_country)->label }}
- @if($search) - {{ $contacts->appends(request()->input())->links() }} - @endif -
- -
-@endif -@endsection diff --git a/resources/views/customers/index.blade.php b/resources/views/customers/index.blade.php deleted file mode 100644 index 5f17d09..0000000 --- a/resources/views/customers/index.blade.php +++ /dev/null @@ -1,80 +0,0 @@ -@extends('layouts.main') - -@section('content') - -

- - {{ __('Customers') }} {{ $site->site_nom }} -

-@if ($errors->any()) -
-
    - @foreach ($errors->all() as $error) -
  • {{ $error }}
  • - @endforeach -
-
-@endif -
-
-
- @csrf - - -
-
- @if (!is_null($email)) - - - - - - - - @if (!count($campaigns)) - - - - @else - - - - - - @endif - -
{{ __('Email') }}{{ __('Pays') }}
{{ __('Aucun résultat.') }}
{{ $customer->email }}{{ Countries::make($customer->country)->label }}Détails
- @foreach ($campaigns as $campaign) -
-

{{ $campaign->camp_name }}

-

Type : {{ $campaign->camp_type->label }}

-
-
-

Pas de contrôle à réinitialiser

-
-
-
- @endforeach - @endif - {{--
-
-

Mur BR Le Gaulois

-

Type : Mur

-
-
-

Pas de contrôle à réinitialiser

-
-
-
-
-

OP Gauloisirs

-

Type : Panier

-
-
- -
-
-
-
--}} -
-@endsection diff --git a/resources/views/export/index.blade.php b/resources/views/export/index.blade.php deleted file mode 100644 index 2d487c3..0000000 --- a/resources/views/export/index.blade.php +++ /dev/null @@ -1,73 +0,0 @@ -@extends('layouts.main') - -@section('content') -

- - {{ __('Export des données coupons') }} -

-
-

{{ __('Période') }} :

- - - - {{ __('Exporter les données') }} -
-@foreach ($sites as $site) -

{{ $site->site_nom }} ({{ $site->site_id }})

- @foreach ($site->coupons as $coupon) -
- - -
- @endforeach -@endforeach - - -@endsection - -@section('scripts') - -@endsection diff --git a/resources/views/export/template.blade.php b/resources/views/export/template.blade.php deleted file mode 100644 index f9ee366..0000000 --- a/resources/views/export/template.blade.php +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - -
-

MesBR.eu

-

{{ $coupon->coupon_name }}

-

{{ __('Diffusions du') }} {{ date('d/m/Y', strtotime($startdate)) }} {{ __('au') }} {{ date('d/m/Y', strtotime($enddate)) }}

- -

{{ __('Détail du coupon') }}

- - - - - - - - - - - - - - - @if($coupon->coupon_mindatediffusion != null) - - @else - - @endif - @if($coupon->coupon_maxdatediffusion != null) - - @else - - @endif - -
{{ __('Nom') }}{{ __('Code') }}{{ __('Quantité totale') }}{{ __('Qté totale diffusée') }}{{ __('Date de début de diffusion') }}{{ __('Date de fin de diffusion') }}
{{ $coupon->coupon_name }}{{ $coupon->coupon_code }}{{ $coupon->couponQuantity }}{{ $coupon->couponFullPrinted }}{{ date('d/m/Y', strtotime($coupon->coupon_mindatediffusion)) }}{{ __('n/c') }}{{ date('d/m/Y', strtotime($coupon->coupon_maxdatediffusion)) }}{{ __('n/c') }}
-

{{ __('Graphique de diffusion sur la période du') }} {{ date('d/m/Y', strtotime($startdate)) }} {{ __('au') }} {{ date('d/m/Y', strtotime($enddate)) }}

-

{{ __('Nombre de coupons diffusés') }} : {{ $coupon->couponPrinted }}

- -
- - diff --git a/resources/views/layouts/header.blade.php b/resources/views/layouts/header.blade.php index d146f40..3aa8336 100644 --- a/resources/views/layouts/header.blade.php +++ b/resources/views/layouts/header.blade.php @@ -1,7 +1,7 @@
- diff --git a/resources/views/layouts/menu.blade.php b/resources/views/layouts/menu.blade.php index 7b45de5..5568075 100644 --- a/resources/views/layouts/menu.blade.php +++ b/resources/views/layouts/menu.blade.php @@ -1,17 +1,17 @@ +@php +$authUserRole = Auth::user()->roles; +@endphp