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