Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | n/a |
0 / 0 |
n/a |
0 / 0 |
CRAP | n/a |
0 / 0 |
|||
| CustomerController | n/a |
0 / 0 |
n/a |
0 / 0 |
31 | n/a |
0 / 0 |
|||
| index | n/a |
0 / 0 |
n/a |
0 / 0 |
13 | |||||
| show | n/a |
0 / 0 |
n/a |
0 / 0 |
1 | |||||
| delete | n/a |
0 / 0 |
n/a |
0 / 0 |
8 | |||||
| export | n/a |
0 / 0 |
n/a |
0 / 0 |
1 | |||||
| exportSubmit | n/a |
0 / 0 |
n/a |
0 / 0 |
8 | |||||
| 1 | <?php |
| 2 | |
| 3 | namespace App\Http\Controllers; |
| 4 | |
| 5 | use App\Enums\CustomerExportType; |
| 6 | use App\Http\Requests\CustomerExport; |
| 7 | use App\Models\Brand; |
| 8 | use App\Models\Customer; |
| 9 | use App\Models\CustomerInfo; |
| 10 | use App\Models\CustomerLogin; |
| 11 | use App\Models\SingleOptin; |
| 12 | use Illuminate\Database\Eloquent\Builder; |
| 13 | use Illuminate\Database\Query\JoinClause; |
| 14 | use Illuminate\Http\Request; |
| 15 | use Illuminate\Support\Carbon; |
| 16 | use Illuminate\Support\Facades\DB; |
| 17 | use Illuminate\Support\Facades\Schema; |
| 18 | use Illuminate\Support\Facades\Session; |
| 19 | use Illuminate\Support\Str; |
| 20 | use League\Csv\Writer; |
| 21 | use Symfony\Component\HttpFoundation\StreamedResponse; |
| 22 | |
| 23 | /** |
| 24 | * @codeCoverageIgnore |
| 25 | */ |
| 26 | class CustomerController extends Controller |
| 27 | { |
| 28 | /** |
| 29 | * View all customers |
| 30 | * |
| 31 | * @param \Illuminate\Http\Request $request |
| 32 | * @return \Illuminate\Http\Response |
| 33 | */ |
| 34 | public function index(Request $request) |
| 35 | { |
| 36 | $modelTable = (new Customer())->getTable(); |
| 37 | $modelClass = Customer::class; |
| 38 | $brands = Brand::select('id', 'name')->get(); |
| 39 | $columns = ['customer_infos.lastname', 'customer_infos.firstname']; |
| 40 | |
| 41 | // * Jointure pour pouvoir trier les colonnes. |
| 42 | /** @var \Illuminate\Database\Eloquent\Builder */ |
| 43 | $query = CustomerInfo::join('customer_logins', function (JoinClause $join) { |
| 44 | $join->on('customer_logins.brand_id', '=', 'customer_infos.brand_id') |
| 45 | ->on('customer_logins.customer_id', '=', 'customer_infos.customer_id'); |
| 46 | }) |
| 47 | ->join('customers', function (JoinClause $join) { |
| 48 | $join->on('customers.id', '=', 'customer_infos.customer_id'); |
| 49 | }) |
| 50 | ->join('brands', function (JoinClause $join) { |
| 51 | $join->on('brands.id', '=', 'customer_infos.brand_id'); |
| 52 | }); |
| 53 | |
| 54 | if ($request->rst) { |
| 55 | Session::remove("crud.$modelTable.brand"); |
| 56 | Session::remove("crud.$modelTable.search"); |
| 57 | Session::remove("crud.$modelTable.sort_way"); |
| 58 | Session::remove("crud.$modelTable.sort_col"); |
| 59 | } |
| 60 | |
| 61 | if (request()->brand or Session::has("crud.$modelTable.brand")) { |
| 62 | $brand = $request->brand ?? Session::get("crud.$modelTable.brand"); |
| 63 | Session::put("crud.$modelTable.brand", $brand); |
| 64 | $query = $query->where('customer_infos.brand_id', $brand); |
| 65 | } |
| 66 | |
| 67 | // * Search on crud Label column. |
| 68 | if ($request->search or Session::has("crud.$modelTable.search")) { |
| 69 | $search = $request->search ?? Session::get("crud.$modelTable.search"); |
| 70 | Session::put("crud.$modelTable.search", $search); |
| 71 | $query->where(function ($q) use ($columns, $search) { |
| 72 | foreach ($columns as $column) { |
| 73 | $q = $q->orWhere($column, 'LIKE', "%$search%"); |
| 74 | } |
| 75 | $q = $q->orWhere('customers.email', 'LIKE', "%$search%"); |
| 76 | $q = $q->orWhere('customer_logins.last_connection', 'LIKE', "%$search%"); |
| 77 | }); |
| 78 | } |
| 79 | |
| 80 | // * Sort columns. |
| 81 | if ($request->sort_col) { |
| 82 | Session::put("crud.$modelTable.sort_col", $request->sort_col); |
| 83 | } |
| 84 | if ($request->sort_way) { |
| 85 | Session::put("crud.$modelTable.sort_way", $request->sort_way); |
| 86 | } |
| 87 | |
| 88 | if ( |
| 89 | Session::has("crud.$modelTable.sort_col") and |
| 90 | Session::has("crud.$modelTable.sort_way") and |
| 91 | (Schema::hasColumn($modelTable, Session::get("crud.$modelTable.sort_col")) or |
| 92 | in_array(Session::get("crud.$modelTable.sort_col"), [ |
| 93 | 'brands.name', |
| 94 | 'customers.email', |
| 95 | 'customer_infos.firstname', |
| 96 | 'customer_infos.lastname', |
| 97 | 'customer_infos.opt_in', |
| 98 | 'customer_logins.last_connection', |
| 99 | 'customer_logins.status' |
| 100 | ])) |
| 101 | ) { |
| 102 | $query = $query->orderBy( |
| 103 | Session::get("crud.$modelTable.sort_col"), |
| 104 | Session::get("crud.$modelTable.sort_way") |
| 105 | ); |
| 106 | } |
| 107 | |
| 108 | $searchFields = trans('Email, Prénom, Nom'); |
| 109 | |
| 110 | /** @var \Illuminate\Pagination\AbstractPaginator */ |
| 111 | $customerModels = $query->paginate(50); |
| 112 | |
| 113 | return view('customers.index', compact( |
| 114 | 'modelClass', |
| 115 | 'modelTable', |
| 116 | 'customerModels', |
| 117 | 'brands', |
| 118 | 'searchFields' |
| 119 | )); |
| 120 | } |
| 121 | |
| 122 | /** |
| 123 | * Show a specific custmer |
| 124 | * |
| 125 | * @param \App\Models\Customer $customer |
| 126 | * @param \App\Models\Brand $brand |
| 127 | * @return \Illuminate\Http\Response |
| 128 | */ |
| 129 | public function show(Customer $customer, Brand $brand) |
| 130 | { |
| 131 | $customerInfos = CustomerInfo::where('customer_id', $customer->id) |
| 132 | ->where('brand_id', $brand->id)->firstOrFail(); |
| 133 | $customerLogin = CustomerLogin::where('customer_id', $customer->id) |
| 134 | ->where('brand_id', $brand->id)->first(); |
| 135 | |
| 136 | return view('customers.show', compact( |
| 137 | 'customerInfos', |
| 138 | 'customerLogin' |
| 139 | )); |
| 140 | } |
| 141 | |
| 142 | /** |
| 143 | * Delete a specific customer info |
| 144 | * |
| 145 | * @param \App\Models\Customer $customer |
| 146 | * @param \App\Models\Brand $brand |
| 147 | * @return \Illuminate\Http\RedirectResponse |
| 148 | */ |
| 149 | public function delete(Customer $customer, Brand $brand) |
| 150 | { |
| 151 | try { |
| 152 | // TODO: Gestion avec mesBR. |
| 153 | /** @var \App\Models\CustomerInfo */ |
| 154 | $customerInfo = $customer->info($brand, Customer::FIRST_OR_FAIL); |
| 155 | /** @var \App\Models\CustomerLogin */ |
| 156 | $customerLogin = $customer->login($brand, Customer::FIRST_OR_FAIL); |
| 157 | |
| 158 | DB::transaction(function () use ( |
| 159 | $brand, |
| 160 | $customer, |
| 161 | $customerLogin, |
| 162 | $customerInfo |
| 163 | ) { |
| 164 | if ( |
| 165 | $customerInfo->delete() === false || |
| 166 | $customerLogin->delete() === false |
| 167 | ) { |
| 168 | throw new \ErrorException('Api failed to delete customer.'); |
| 169 | }; |
| 170 | |
| 171 | $customer->optInHistory()->whereHas('brand', function ($q) use ($brand) { |
| 172 | $q->where('id', $brand->id); |
| 173 | })->delete(); |
| 174 | |
| 175 | $logins = $customer->logins()->count(); |
| 176 | $infos = $customer->infos()->count(); |
| 177 | $history = $customer->optInHistory()->count(); |
| 178 | |
| 179 | // If customer is not associated with any brand then delete. |
| 180 | if (!$logins and !$infos and !$history) { |
| 181 | if ($customer->delete() === false) { |
| 182 | throw new \ErrorException('Api failed to update customer.'); |
| 183 | } |
| 184 | } |
| 185 | }); |
| 186 | } catch (\ErrorException $e) { |
| 187 | return redirect()->route('customers.index') |
| 188 | ->with('error', trans('Le compte n\'a pas pu être supprimé.')); |
| 189 | } //end try |
| 190 | |
| 191 | return redirect()->route('customers.index')->with('warning', trans('Compte supprimé.')); |
| 192 | } |
| 193 | |
| 194 | /** |
| 195 | * Show export the customers form |
| 196 | * |
| 197 | * @return \Illuminate\Http\Response |
| 198 | */ |
| 199 | public function export() |
| 200 | { |
| 201 | $brands = Brand::all(); |
| 202 | return view('customers.export', compact( |
| 203 | 'brands' |
| 204 | )); |
| 205 | } |
| 206 | |
| 207 | /** |
| 208 | * Exports customers following the requested export type |
| 209 | * |
| 210 | * @param \App\Http\Requests\CustomerExport $request |
| 211 | * @return \Symfony\Component\HttpFoundation\StreamedResponse |
| 212 | * @throws \Exception If the request export type in not handled. |
| 213 | */ |
| 214 | public function exportSubmit(CustomerExport $request) |
| 215 | { |
| 216 | $pagination = 2000; |
| 217 | $header = '"lastname";"firstname";"email";"hashcode"'; |
| 218 | |
| 219 | $w = Writer::createFromString($header . PHP_EOL); |
| 220 | $w->setDelimiter(';'); |
| 221 | $w->setEnclosure('"'); |
| 222 | $w->setOutputBOM(Writer::BOM_UTF8); |
| 223 | |
| 224 | $exportType = CustomerExportType::make(\intval($request->export_type)); |
| 225 | |
| 226 | // * We send the CSV using Symfony StreamedResponse. |
| 227 | $response = new StreamedResponse(); |
| 228 | $response->headers->set('Content-Encoding', 'none'); |
| 229 | $response->headers->set('Content-Type', 'text/csv; charset=UTF-8'); |
| 230 | $response->headers->set('Description', 'File Transfer'); |
| 231 | $response->headers->set( |
| 232 | 'Content-Disposition', |
| 233 | sprintf( |
| 234 | 'attachment; filename="Export pour newsletter %s %s.csv"', |
| 235 | Carbon::now()->format('d-m-Y_H-i'), |
| 236 | Str::slug($exportType) |
| 237 | ) |
| 238 | ); |
| 239 | |
| 240 | switch ($exportType) { |
| 241 | case CustomerExportType::both(): |
| 242 | $continue = true; |
| 243 | // ! Continue |
| 244 | case CustomerExportType::customer(): |
| 245 | $page = 1; |
| 246 | /** @var \Illuminate\Database\Eloquent\Builder $req */ |
| 247 | $req = CustomerInfo::where('brand_id', $request->brand) |
| 248 | ->where('opt_in', true); |
| 249 | $total = $req->count(); |
| 250 | $pages = \ceil($total / $pagination); |
| 251 | do { |
| 252 | /** @var \Illuminate\Pagination\LengthAwarePaginator $items */ |
| 253 | $items = $req->with('customer', function ($q) { |
| 254 | $q->select(['id', 'email']); |
| 255 | })->paginate( |
| 256 | $pagination, |
| 257 | ['lastname', 'firstname', 'hashcode', 'customer_id'], |
| 258 | 'page', |
| 259 | $page |
| 260 | ); |
| 261 | $items->map(function (CustomerInfo $customerInfo) use ($w) { |
| 262 | $w->insertOne([ |
| 263 | 'lastname' => $customerInfo->lastname, |
| 264 | 'firstname' => $customerInfo->firstname, |
| 265 | 'email' => $customerInfo->customer->email, |
| 266 | 'hashcode' => $customerInfo->hashcode |
| 267 | ]); |
| 268 | }); |
| 269 | } while ($page++ <= $pages); |
| 270 | if (!isset($continue)) { |
| 271 | break; |
| 272 | } |
| 273 | // ! Continue |
| 274 | case CustomerExportType::singleOptin(): |
| 275 | SingleOptin::query()->whereHas('brand', function (Builder $q) use ($request) { |
| 276 | $q->where('id', $request->brand); |
| 277 | })->get()->map(function (SingleOptin $singleOptin) use ($w) { |
| 278 | $w->insertOne([ |
| 279 | 'lastname' => '', |
| 280 | 'firstname' => '', |
| 281 | 'email' => $singleOptin->email, |
| 282 | 'hashcode' => $singleOptin->hashcode |
| 283 | ]); |
| 284 | }); |
| 285 | break; |
| 286 | default: |
| 287 | throw new \Exception(sprintf('Unhandled CustomerExportType %s', $exportType->label)); |
| 288 | } //end switch |
| 289 | |
| 290 | /* |
| 291 | We create a callable to output the CSV in chunk |
| 292 | with Symfony StreamResponse you can flush the body content if necessary |
| 293 | see Symfony documentation for more information |
| 294 | */ |
| 295 | |
| 296 | // * The flush value should depend on your CSV size. |
| 297 | $flush_threshold = 1000; |
| 298 | |
| 299 | $response->setCallback(function () use ($w, $flush_threshold) { |
| 300 | foreach ($w->chunk(1024) as $offset => $chunk) { |
| 301 | echo $chunk; |
| 302 | if ($offset % $flush_threshold === 0) { |
| 303 | flush(); |
| 304 | } |
| 305 | } |
| 306 | }); |
| 307 | return $response; |
| 308 | } |
| 309 | } |