455 lines
16 KiB
PHP
455 lines
16 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Bo;
|
|
|
|
use App\Enums\CustomerExportType;
|
|
use App\Http\Controllers\Controller;
|
|
use App\Http\Requests\Bo\CustomerExport;
|
|
use App\Models\Answer;
|
|
use App\Models\Brand;
|
|
use App\Models\Customer;
|
|
use App\Models\CustomerInfo;
|
|
use App\Models\CustomerLogin;
|
|
use App\Models\Question;
|
|
use App\Models\SingleOptin;
|
|
use App\Models\BrandContactList;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Database\Query\JoinClause;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Support\Facades\Session;
|
|
use Illuminate\Support\Str;
|
|
use League\Csv\Writer;
|
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
|
use App\Lib\Helpers\MailjetHelper;
|
|
|
|
class CustomerController extends Controller
|
|
{
|
|
/**
|
|
* Create the controller instance.
|
|
*/
|
|
public function __construct()
|
|
{
|
|
$this->authorizeResource(Customer::class);
|
|
}
|
|
|
|
/**
|
|
* View all customers.
|
|
*
|
|
* @param \Illuminate\Http\Request $request
|
|
* @return \Illuminate\Contracts\View\View
|
|
*/
|
|
public function index(Request $request): \Illuminate\Contracts\View\View
|
|
{
|
|
$modelClass = Customer::class;
|
|
$modelTable = (new Customer())->getTable();
|
|
$questionModels = null;
|
|
$answerModels = null;
|
|
$searchFields = trans('Email, Prénom, Nom');
|
|
|
|
// Reset session sorts.
|
|
if ($request->rstSrts) {
|
|
$this->resetSessionSorts($modelTable);
|
|
}
|
|
// Reset session filters.
|
|
if ($request->rstFltrs) {
|
|
$this->resetSessionFilters($modelTable);
|
|
}
|
|
|
|
if ($request->filled('search') || Session::get("crud.$modelTable.search")) {
|
|
$columns = ['customer_infos.lastname', 'customer_infos.firstname'];
|
|
$query = $this->buildCustomerQuery();
|
|
|
|
// Apply filters.
|
|
$this->applyBrandFilter($request, $query, $modelTable, $questionModels, $answerModels);
|
|
$this->applySearchFilter($request, $query, $modelTable, $columns);
|
|
$this->applySort($request, $query, $modelTable);
|
|
|
|
$customerModels = $query->with(['customer', 'brand'])->simplePaginate($this->getPagination());
|
|
} //end if
|
|
|
|
return view('bo.pages.customers.index', [
|
|
'modelClass' => $modelClass,
|
|
'modelTable' => $modelTable,
|
|
'customerModels' => $customerModels ?? collect(),
|
|
'searchFields' => $searchFields,
|
|
'questionModels' => $questionModels,
|
|
'answerModels' => $answerModels,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Show a specific custmer.
|
|
*
|
|
* @param \App\Models\Customer $customer
|
|
* @param \App\Models\Brand $brand
|
|
* @return \Illuminate\Contracts\View\View
|
|
*/
|
|
public function show(Customer $customer, Brand $brand): \Illuminate\Contracts\View\View
|
|
{
|
|
$customerInfos = CustomerInfo::query()->where('customer_id', $customer->id)
|
|
->where('brand_id', $brand->id)->firstOrFail();
|
|
$customerLogin = CustomerLogin::query()->where('customer_id', $customer->id)
|
|
->where('brand_id', $brand->id)->first();
|
|
|
|
return view('bo.pages.customers.show', compact(
|
|
'customerInfos',
|
|
'customerLogin'
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Delete a specific customer info.
|
|
*
|
|
* @param \App\Models\Customer $customer
|
|
* @param \App\Models\Brand $brand
|
|
* @return \Illuminate\Http\RedirectResponse
|
|
*/
|
|
public function delete(Customer $customer, Brand $brand): \Illuminate\Http\RedirectResponse
|
|
{
|
|
try {
|
|
/** @var \App\Models\CustomerInfo */
|
|
$customerInfo = $customer->info($brand, Customer::FIRST_OR_FAIL);
|
|
/** @var \App\Models\CustomerLogin */
|
|
$customerLogin = $customer->login($brand, Customer::FIRST_OR_FAIL);
|
|
|
|
DB::transaction(function () use (
|
|
$brand,
|
|
$customer,
|
|
$customerLogin,
|
|
$customerInfo
|
|
) {
|
|
if (
|
|
$customerInfo->delete() === false ||
|
|
$customerLogin->delete() === false
|
|
) {
|
|
throw new \ErrorException('Api failed to delete customer.');
|
|
};
|
|
|
|
$customer->optInHistory()->whereHas('brand', function ($q) use ($brand) {
|
|
$q->where('id', $brand->id);
|
|
})->delete();
|
|
|
|
$logins = $customer->logins()->count();
|
|
$infos = $customer->infos()->count();
|
|
$history = $customer->optInHistory()->count();
|
|
|
|
// If customer is not associated with any brand then delete.
|
|
if (!$logins and !$infos and !$history) {
|
|
if ($customer->delete() === false) {
|
|
throw new \ErrorException('Api failed to update customer.');
|
|
}
|
|
}
|
|
});
|
|
} catch (\ErrorException $e) {
|
|
return redirect()->route('bo.customers.index')
|
|
->with('error', trans('Le compte n\'a pas pu être supprimé.'));
|
|
} //end try
|
|
|
|
return redirect()->route('bo.customers.index')->with('warning', trans('Compte supprimé.'));
|
|
}
|
|
|
|
/**
|
|
* Show export the customers form.
|
|
*
|
|
* @return \Illuminate\Contracts\View\View
|
|
*/
|
|
public function export(): \Illuminate\Contracts\View\View
|
|
{
|
|
$this->authorize('export', Customer::class);
|
|
$brands = Brand::all();
|
|
return view('bo.pages.customers.export', compact(
|
|
'brands'
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Exports customers following the requested export type.
|
|
*
|
|
* @param \App\Http\Requests\Bo\CustomerExport $request
|
|
* @return \Symfony\Component\HttpFoundation\StreamedResponse|\Illuminate\Http\RedirectResponse
|
|
* @throws \Exception If the request export type in not handled.
|
|
*/
|
|
public function exportSubmit(CustomerExport $request): \Symfony\Component\HttpFoundation\StreamedResponse|\Illuminate\Http\RedirectResponse
|
|
{
|
|
$pagination = 2000;
|
|
|
|
if ($request->action === "export") {
|
|
$header = '"lastname";"firstname";"email";"hashcode"';
|
|
|
|
$w = Writer::createFromString($header . PHP_EOL);
|
|
$w->setDelimiter(';');
|
|
} else {
|
|
$header = 'lastname,firstname,email,hashcode';
|
|
|
|
$w = Writer::createFromString($header . PHP_EOL);
|
|
$w->setDelimiter(',');
|
|
}
|
|
|
|
$w->setEnclosure('"');
|
|
$w->setOutputBOM(Writer::BOM_UTF8);
|
|
|
|
$exportType = CustomerExportType::make(\intval($request->export_type));
|
|
|
|
// * We send the CSV using Symfony StreamedResponse.
|
|
$response = new StreamedResponse();
|
|
$response->headers->set('Content-Encoding', 'none');
|
|
$response->headers->set('Content-Type', 'text/csv; charset=UTF-8');
|
|
$response->headers->set('Description', 'File Transfer');
|
|
$response->headers->set(
|
|
'Content-Disposition',
|
|
sprintf(
|
|
'attachment; filename="Export pour newsletter %s %s.csv"',
|
|
Carbon::now()->isoFormat('DD-MM-YYYY_HH-mm'),
|
|
Str::slug($exportType->label())
|
|
)
|
|
);
|
|
|
|
switch ($exportType) {
|
|
case CustomerExportType::both:
|
|
$continue = true;
|
|
// ! Continue
|
|
case CustomerExportType::customer:
|
|
$page = 1;
|
|
/** @var \Illuminate\Database\Eloquent\Builder $req */
|
|
$req = CustomerInfo::query()->where('brand_id', $request->brand)
|
|
->where('opt_in', true);
|
|
$total = $req->count();
|
|
$pages = \ceil($total / $pagination);
|
|
do {
|
|
/** @var \Illuminate\Pagination\LengthAwarePaginator $items */
|
|
$items = $req->with('customer', function ($q) {
|
|
$q->select(['id', 'email']);
|
|
})->paginate(
|
|
$pagination,
|
|
['lastname', 'firstname', 'hashcode', 'customer_id'],
|
|
'page',
|
|
$page
|
|
);
|
|
$items->map(function (CustomerInfo $customerInfo) use ($w) {
|
|
$w->insertOne([
|
|
'lastname' => $customerInfo->lastname,
|
|
'firstname' => $customerInfo->firstname,
|
|
'email' => $customerInfo->customer->email,
|
|
'hashcode' => $customerInfo->hashcode
|
|
]);
|
|
});
|
|
} while ($page++ <= $pages);
|
|
if (!isset($continue)) {
|
|
break;
|
|
}
|
|
// ! Continue
|
|
case CustomerExportType::singleOptin:
|
|
SingleOptin::query()->whereHas('brand', function (Builder $q) use ($request) {
|
|
$q->where('id', $request->brand);
|
|
})->get()->map(function (SingleOptin $singleOptin) use ($w) {
|
|
$w->insertOne([
|
|
'lastname' => '',
|
|
'firstname' => '',
|
|
'email' => $singleOptin->email,
|
|
'hashcode' => $singleOptin->hashcode
|
|
]);
|
|
});
|
|
break;
|
|
default:
|
|
throw new \Exception(sprintf('Unhandled CustomerExportType %s', $exportType->label()));
|
|
} //end switch
|
|
|
|
if ($request->action === "export") {
|
|
/*
|
|
We create a callable to output the CSV in chunk
|
|
with Symfony StreamResponse you can flush the body content if necessary
|
|
see Symfony documentation for more information
|
|
*/
|
|
|
|
// * The flush value should depend on your CSV size.
|
|
$flush_threshold = 1000;
|
|
|
|
$response->setCallback(function () use ($w, $flush_threshold) {
|
|
foreach ($w->chunk(1024) as $offset => $chunk) {
|
|
echo $chunk;
|
|
if ($offset % $flush_threshold === 0) {
|
|
flush();
|
|
}
|
|
}
|
|
});
|
|
|
|
return $response;
|
|
} else {
|
|
$brandModel = Brand::find($request->brand);
|
|
$contactList = MailjetHelper::createContactList(sprintf("%s - %s", now()->format('d/m/Y'), $brandModel->name));
|
|
|
|
if (empty($contactList)) {
|
|
return back()->withErrors('Impossible de créer la liste de contacts Mailjet.');
|
|
}
|
|
|
|
$mailjetListId = $contactList[0]['ID'];
|
|
|
|
BrandContactList::create([
|
|
'brand_id' => $brandModel->getKey(),
|
|
'mailjet_list_id' => $mailjetListId,
|
|
]);
|
|
|
|
$csvData = MailjetHelper::uploadCsvFile($mailjetListId, $w->toString());
|
|
$importJob = MailjetHelper::importContacts($mailjetListId, $csvData['ID']);
|
|
|
|
return back()->with('success', 'Liste créée et contacts importés avec succès.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reset all session sorts.
|
|
*
|
|
* @param string $table
|
|
* @return void
|
|
*/
|
|
private function resetSessionSorts(string $table): void
|
|
{
|
|
Session::forget([
|
|
"crud.$table.sort_col",
|
|
"crud.$table.sort_way",
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Reset all session filters.
|
|
*
|
|
* @param string $table
|
|
* @return void
|
|
*/
|
|
private function resetSessionFilters(string $table): void
|
|
{
|
|
Session::forget([
|
|
"crud.$table.brand",
|
|
"crud.$table.question",
|
|
"crud.$table.answer",
|
|
"crud.$table.search",
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Build the customer query.
|
|
*
|
|
* @return Builder
|
|
*/
|
|
private function buildCustomerQuery(): Builder
|
|
{
|
|
return CustomerInfo::query()
|
|
->join('customer_logins', function (JoinClause $join) {
|
|
$join->on('customer_logins.brand_id', '=', 'customer_infos.brand_id')
|
|
->on('customer_logins.customer_id', '=', 'customer_infos.customer_id');
|
|
})
|
|
->join('customers', fn($join) => $join->on('customers.id', '=', 'customer_infos.customer_id'))
|
|
->join('brands', fn($join) => $join->on('brands.id', '=', 'customer_infos.brand_id'));
|
|
}
|
|
|
|
/**
|
|
* Apply all filters (brand, question, answer).
|
|
*
|
|
* @param \Illuminate\Http\Request $request
|
|
* @param \Illuminate\Database\Eloquent\Builder $query
|
|
* @param string $table
|
|
* @param \Illuminate\Support\Collection|null $questionModels
|
|
* @param \Illuminate\Support\Collection|null $answerModels
|
|
* @return void
|
|
*/
|
|
private function applyBrandFilter(
|
|
Request $request,
|
|
Builder &$query,
|
|
string $table,
|
|
?Collection &$questionModels,
|
|
?Collection &$answerModels
|
|
): void {
|
|
$brand = $request->brand ?? Session::get("crud.$table.brand");
|
|
if (!(int) $brand) {
|
|
return;
|
|
}
|
|
Session::put("crud.$table.brand", $brand);
|
|
$query->where('customer_infos.brand_id', $brand);
|
|
|
|
$questionModels = Question::getAllModelsFromBrand($brand);
|
|
|
|
if (($request->filled('question') || Session::has("crud.$table.question")) && $questionModels->count()) {
|
|
$question = $request->question ?? Session::get("crud.$table.question");
|
|
Session::put("crud.$table.question", $question);
|
|
|
|
$answerModels = Answer::getAllAnswersFromQuestion($question);
|
|
$questionAnswers = Question::find($question)?->answers->modelKeys() ?? [];
|
|
|
|
$query->whereHas('customer.answers', fn($q) => $q->whereIn('answer_id', $questionAnswers));
|
|
|
|
if (
|
|
($request->filled('answer') || Session::has("crud.$table.answer"))
|
|
&& $questionModels->count()
|
|
&& $answerModels->count()
|
|
) {
|
|
$answer = $request->answer ?? Session::get("crud.$table.answer");
|
|
Session::put("crud.$table.answer", $answer);
|
|
|
|
$query->whereHas('customer.answers', fn($q) => $q->whereKey($answer));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Apply the search filter.
|
|
*
|
|
* @param \Illuminate\Http\Request $request
|
|
* @param \Illuminate\Database\Eloquent\Builder $query
|
|
* @param string $table
|
|
* @param array $columns
|
|
* @return void
|
|
*/
|
|
private function applySearchFilter(Request $request, Builder &$query, string $table, array $columns): void
|
|
{
|
|
$search = $request->search ?? Session::get("crud.$table.search");
|
|
Session::put("crud.$table.search", $search);
|
|
|
|
$query->where(function ($q) use ($columns, $search) {
|
|
foreach ($columns as $column) {
|
|
$q->orWhere($column, 'LIKE', "%$search%");
|
|
}
|
|
|
|
$q->orWhere('customers.email', 'LIKE', "%$search%")
|
|
->orWhere('customer_logins.last_connection', 'LIKE', "%$search%");
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Apply sorts.
|
|
*
|
|
* @param \Illuminate\Http\Request $request
|
|
* @param \Illuminate\Database\Eloquent\Builder $query
|
|
* @param string $table
|
|
* @return void
|
|
*/
|
|
private function applySort(Request $request, Builder &$query, string $table): void
|
|
{
|
|
$sortCol = $request->sort_col ?? Session::get("crud.$table.sort_col");
|
|
$sortWay = $request->sort_way ?? Session::get("crud.$table.sort_way");
|
|
|
|
if ($sortCol) {
|
|
Session::put("crud.$table.sort_col", $sortCol);
|
|
}
|
|
if ($sortWay) {
|
|
Session::put("crud.$table.sort_way", $sortWay);
|
|
}
|
|
|
|
$sortable = [
|
|
'brands.name',
|
|
'customers.email',
|
|
'customer_infos.firstname',
|
|
'customer_infos.lastname',
|
|
'customer_infos.opt_in',
|
|
'customer_logins.last_connection',
|
|
'customer_logins.status',
|
|
];
|
|
|
|
if ($sortCol && $sortWay && (Schema::hasColumn($table, $sortCol) || in_array($sortCol, $sortable))) {
|
|
$query->orderBy($sortCol, $sortWay);
|
|
}
|
|
}
|
|
}
|