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