<?php
namespace App\Controller;
use App\Entity\Certificate;
use App\Entity\HistorySend;
use App\Entity\Order;
use App\Entity\OrderItem;
use App\Entity\LifeCycle;
use App\Enums\ResponseStatusEnum;
use App\Form\OrderSearchType;
use App\Form\OrderType;
use App\Repository\CertificateRepository;
use App\Repository\HistorySendRepository;
use App\Repository\LifeCycleRepository;
use App\Repository\OrderRepository;
use App\Repository\PromoCodesRepository;
use App\Repository\WidgetRepository;
use App\Repository\WidgetUserRepository;
use App\Entity\WidgetUser;
use App\Services\Contracts\OrderServiceInterface;
use App\Services\LogService\CertificateLogService;
use Doctrine\ORM\EntityManagerInterface;
use Error;
use Exception;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Validator\Constraints\Uuid;
use Symfony\Component\Validator\Validation;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Throwable;
class OrderController extends AbstractController
{
/**
* @param Request $request
* @param OrderRepository $orderRepository
* @param WidgetUserRepository $userRepository
* @param WidgetRepository $widgetRepository
* @return Response
* @Route("/backend/orders/statistics", name="backend_order_statistics")
* @Security("is_granted('ROLE_CLIENT') or is_granted('ROLE_CALLCENTER') or is_granted('ROLE_MERCHANT') or is_granted('ROLE_MANAGER') or is_granted('ROLE_SUPERADMINISTRATOR')")
*/
public function statistics(Request $request, OrderRepository $orderRepository, WidgetUserRepository $userRepository, WidgetRepository $widgetRepository): Response
{
$form = $this->createForm('App\Form\StatisticsSearchType');
$form->handleRequest($request);
if (!$this->isGranted('ROLE_SUPERADMINISTRATOR')) {
$user = $userRepository->find($this->getUser()->getId());
$widgets = $user->getAvailableWidgets();
} else {
$widgets = $widgetRepository->findAll();
}
$createView = $form->createView();
if(!empty($createView->children['Widget'])) {
$keys = [];
foreach ($createView->children['Widget']->vars['choices'] as $key => $choice)
foreach ($widgets as $widget)
if ($widget->getId() == $choice->value)
$keys[] = $key;
foreach ($createView->children['Widget']->vars['choices'] as $key => $row)
if (!in_array($key, $keys))
unset($createView->children['Widget']->vars['choices'][$key]);
}
return $this->render('order/statistics.html.twig', [
'statistics_search' => $createView,
'orders' => [],
'orders_count' => 0,
'amount_orders' => 0,
'average_check' => '',
'sales_count' => 0,
'get_params' => (!empty($_GET['order_search']['columns'])?$_GET['order_search']['columns']:Order::FIELDS),
'total' => 0,
'pages' => 0,
'widgets' => $createView->children['Widget']->vars['choices'],
'page' => 0,
'params' => '&'.preg_replace('/(&page|page)=[0-9]/', '', $request->getQueryString())
]);
}
/**
* @param Request $request
* @param OrderRepository $orderRepository
* @param WidgetUserRepository $userRepository
* @param CertificateRepository $certificateRepository
* @param WidgetRepository $widgetRepository
* @return Response
* @Security("is_granted('ROLE_CLIENT') or is_granted('ROLE_CALLCENTER') or is_granted('ROLE_MERCHANT') or is_granted('ROLE_MANAGER') or is_granted('ROLE_SUPERADMINISTRATOR')")
*/
#[Route('/backend/orders/statistics_data', name: "backend_order_statistics_data", methods: ['GET'])]
public function statisticsData(Request $request, OrderRepository $orderRepository, WidgetUserRepository $userRepository, CertificateRepository $certificateRepository, WidgetRepository $widgetRepository): Response
{
$form = $this->createForm('App\Form\StatisticsSearchType');
$form->handleRequest($request);
$user = $userRepository->find($this->getUser()->getId());
$getData = $form->getData();
$getData['paymentStatus'] = ['succeeded'];
$getData['statistics'] = 1;
$result = [];
if($form->isSubmitted() && $form->isValid()) {
$result = $orderRepository->getListFilteredOrders(
$user,
$getData,
$request->get('page', 1));
}
$daysOrders = [];
$ordersCount = $certificationsCount = $ordersSum = $smsCount = $emailCount = $otherCount = $meCount = 0;
$orderItemIds = [];
foreach($result['items'] as $row){
$orderItemIds[] = $row['id'];
$dataItem = [
'amount' => $row['amount'],
'quantity' => $row['quantity'],
'sum' => $row['amount']*$row['quantity'],
'recipient_type' => $row['recipientType'],
'delivery_type' => $row['deliveryType']
];
$daysOrders['orders'][$row['createdAt']->format('Y-m-d')][] = $dataItem;
$ordersCount++;
$otherCount += ($dataItem['recipient_type'] == 'other')?1:0;
$meCount += ($dataItem['recipient_type'] == 'me')?1:0;
$certificationsCount += $dataItem['quantity'] ?? 0;
$ordersSum += $dataItem['sum'] ?? 0;
$smsCount += (isset($dataItem['delivery_type'][0]) && $dataItem['delivery_type'][0] == 'sms')?1:0;
$emailCount += (isset($dataItem['delivery_type'][0]) && $dataItem['delivery_type'][0] == 'email')?1:0;
}
$amountsCount = [];
$nominals = $certificateRepository->getCountNominalsByOrderItemIds($orderItemIds);
foreach($nominals as $nominal){
$amountsCount[$nominal['nominal']] = $nominal['count'];
}
$averageAmount = ($ordersSum && $ordersCount)?($ordersSum/$ordersCount):0;
$daysOrders['data']=[
'orders_count' => $ordersCount,
'certifications_count' => $certificationsCount,
'orders_sum' => $ordersSum,
'average_amount' => $averageAmount,
'other_count' => $otherCount,
'me_count' => $meCount,
'sms_count' => $smsCount,
'email_count' => $emailCount,
'nominals' => $amountsCount
];
return $this->json($daysOrders);
}
/**
* @param Request $request
* @param OrderRepository $orderRepository
* @param WidgetUserRepository $userRepository
* @param WidgetRepository $widgetRepository
* @param LifeCycleRepository $lifeCycleRepository
* @param PromoCodesRepository $promoCodesRepository
* @return Response
* @Route("/backend/orders", name="backend_order_index")
* @Security("is_granted('ROLE_CLIENT') or is_granted('ROLE_CALLCENTER') or is_granted('ROLE_MERCHANT') or is_granted('ROLE_MANAGER') or is_granted('ROLE_SUPERADMINISTRATOR')")
*/
public function index(Request $request, OrderRepository $orderRepository, WidgetUserRepository $userRepository, WidgetRepository $widgetRepository, LifeCycleRepository $lifeCycleRepository, PromoCodesRepository $promoCodesRepository): Response
{
$user = $userRepository->find($this->getUser()->getId());
if (!$this->isGranted('ROLE_SUPERADMINISTRATOR')) {
$widgets = $user->getAvailableWidgets()->toArray();
} else {
$widgets = $widgetRepository->findAll();
}
$filterWidget = [];
foreach ($widgets as $widget) {
$filterWidget[] = [$widget->getName() => $widget->getId()->toString()];
}
$form = $this->createForm(OrderSearchType::class, null, ['filter_widget' => $filterWidget]);
$form->handleRequest($request);
$user = $userRepository->find($this->getUser()->getId());
$errors = [];
$orderId = $form->get('orderId')->getData();
if (!empty($orderId)) {
$validator = Validation::createValidator();
$uuidConstrain = new Uuid();
$errorUid = $validator->validate($orderId, $uuidConstrain);
if (0 !== count($errorUid)){
$errors['orderId'] = 'Некорректный № заказа Виджета';
$this->addFlash('error', 'Некорректный формат № заказа Виджета');
}
}
if(0 == count($errors) && $form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
if(!empty($data['promoCodeId'])){
$promoCodes = $promoCodesRepository->findBy(['code' => $data['promoCodeId']]);
foreach($promoCodes as $promoCode){
$promoCodeIds[] = $promoCode->getId();
}
$data['promoCodeId'] = $promoCodeIds ?? [0];
} else {
$data['promoCodeId'] = null;
}
$result = $orderRepository->getListFilteredOrders(
$user,
$data,
$request->get('page', 1)
);
} else {
$result = $orderRepository->getListOrdersByUser(
$user,
$request->get('page', 1)
);
}
$createView = $form->createView();
foreach ($result['items'] as &$item) {
$promoCode = $promoCodesRepository->findOneBy(['id' => $item->getPromoCodeId()], ['id' => 'DESC']);
$item->lifeStatusName = $item->getLifeStatus() ? LifeCycle::STATUS_LIFE[$item->getLifeStatus()] : '';
$item->promoCode = !empty($promoCode) ? $promoCode->getCode() : '';
$item->deliveryStatus = isset(
LifeCycle::ORDER_STATUS[$item->getLifeStatus()]
) ? LifeCycle::ORDER_STATUS[$item->getLifeStatus()] : 'Формируется';
foreach($item->getOrderItems() as $orderItem){
$delayedSending = $orderItem->getTimeToSend()?->format("d.m.Y H:i:s");
$orderItem->delayedSending = $item->getCreatedAt() != $orderItem->getTimeToSend() ? $delayedSending : 'Нет';
}
}
return $this->render('order/report.html.twig', [
'order_search' => $createView,
'orders' => $result['items'],
'get_params' => (!empty($_GET['order_search']['columns'])?$_GET['order_search']['columns']:Order::FIELDS),
'total' => $result['total'],
'pages' => $result['pages'],
'page' => $request->get('page', 1),
'params' => '&'.preg_replace('/(&page|page)=[0-9]/', '', $request->getQueryString())
]);
}
/**
* @Route("/backend/orders/csv", name="backend_order_csv")
* @Security("is_granted('ROLE_CLIENT') or is_granted('ROLE_MERCHANT') or is_granted('ROLE_MANAGER') or is_granted('ROLE_SUPERADMINISTRATOR')")
*/
public function exportCSV(Request $request, OrderRepository $orderRepository, WidgetUserRepository $userRepository, WidgetRepository $widgetRepository, PromoCodesRepository $promoCodesRepository): Response
{
$user = $userRepository->find($this->getUser()->getId());
if (!$this->isGranted('ROLE_SUPERADMINISTRATOR')) {
$widgets = $user->getAvailableWidgets()->toArray();
} else {
$widgets = $widgetRepository->findAll();
}
$filterWidget = [];
foreach ($widgets as $widget) {
$filterWidget[] = [$widget->getName() => $widget->getId()->toString()];
}
$form = $this->createForm(OrderSearchType::class, null, ['filter_widget' => $filterWidget]);
$form->handleRequest($request);
// Определяем поля для экспорта
$selectedFieldsRaw = $request->query->get('order_search')['columns'] ?? Order::FIELDS;
$fields = array_filter(Order::FIELDS, fn($field) => in_array($field, $selectedFieldsRaw));
// === Собираем ВСЕ заказы без пагинации ===
if ($form->isSubmitted() && $form->isValid()) {
// Обходим пагинацию: используем копию данных, но без page/limit
$fromData = $form->getData();
if($fromData['promoCodeId']){
$promoCodes = $promoCodesRepository->findBy(['code' => $fromData['promoCodeId']]);
$promoCodeIds = [];
foreach($promoCodes as $promoCode){
$promoCodeIds[] = $promoCode->getId();
}
$fromData['promoCodeId'] = $promoCodeIds ?? [0];
}
$allOrders = $this->fetchAllOrders($orderRepository, $user, $fromData);
} else {
$allOrders = $this->fetchAllOrders($orderRepository, $user, null);
}
// Генерация CSV как поток (StreamedResponse) — экономит память
$callback = function () use ($allOrders, $fields) {
$output = fopen('php://output', 'w');
// BOM для Excel
fwrite($output, "\xEF\xBB\xBF");
// === Заголовки ===
$headers = [
'widgetName' => 'Виджет',
'createdAt' => 'Дата создания',
'updatedAt' => 'Дата обновления',
'recipientType' => 'Кому',
'deliveryType' => 'Способ Отправки',
'recipientName' => 'Имя получателя',
'senderName' => 'Имя отправителя',
'tiberiumOrderId' => '№ заказа в ПЦ',
'widgetOrderId' => '№ заказа Виджета',
'getStatusText' => 'Статус оплаты',
'delayedSending' => 'Отложенная отправка',
'senderEmail' => 'Email отправителя',
'recipientEmail' => 'E-mail/Телефон Получателя',
'paymentDataId' => 'ID Платежа',
'quantity' => 'Кол-во',
'amount' => 'Стоимость руб.',
'summ' => 'Общая сумма руб.',
'promoCodeId' => 'Промокод',
'utmSource' => 'UTM Source',
'utmContent' => 'UTM Content',
];
$headerRow = [];
foreach ($fields as $field) {
$headerRow[] = $headers[$field] ?? $field;
}
fprintf($output, chr(0xEF).chr(0xBB).chr(0xBF));
fputcsv($output, $headerRow, ';', '"');
// === Данные ===
foreach ($allOrders as $order) {
foreach ($order->getOrderItems() as $item) {
$row = [];
foreach ($fields as $field) {
$value = $this->getFieldValue($order, $item, $field);
$row[] = $value ?? '';
}
fputcsv($output, $row, ';', '"');
}
}
fclose($output);
};
return new StreamedResponse($callback, 200, [
'Content-Type' => 'text/csv; charset=UTF-8',
'Content-Disposition' => 'attachment; filename="orders_' . date('Y-m-d') . '.csv"',
]);
}
private function fetchAllOrders(OrderRepository $repo, WidgetUser $user, ?array $formData): array
{
if ($formData !== null) {
// Обходим пагинацию: вызываем метод без page
// Но если LIMIT применяется — нужно переделать репо!
// Временное решение: делаем цикл по страницам (не идеально, но работает)
$all = [];
$page = 1;
do {
$result = $repo->getListFilteredOrders($user, $formData, $page);
$all = array_merge($all, $result['items']);
$page++;
} while ($result['items']);
return $all;
} else {
$all = [];
$page = 1;
do {
$result = $repo->getListOrdersByUser($user, $page);
$all = array_merge($all, $result['items']);
$page++;
} while ($result['items']);
return $all;
}
}
private function getFieldValue(Order $order, OrderItem $item, string $field): ?string
{
return match($field) {
'widgetName' => (string) ($order->getWidget()?->getName() ?? ''),
'createdAt' => $order->getCreatedAt()->format('Y-m-d H:i:s'),
'updatedAt' => $order->getUpdatedAt()?->format('Y-m-d H:i:s') ?? '',
'recipientType' => $item->getRecipientType(),
'deliveryType' => is_array($item->getDeliveryType())
? implode(',', $item->getDeliveryType())
: (string) $item->getDeliveryType(),
'recipientName' => $item->getRecipientName(),
'senderName' => $item->getSenderName(),
'tiberiumOrderId' => (string) $item->getTiberiumOrderId() ? "'".$item->getTiberiumOrderId() : '',
'widgetOrderId' => (string) $order->getId(),
'getStatusText' => (string) $order->getStatusText(),
'senderEmail' => $item->getSenderEmail(),
'recipientEmail' => $item->getRecipientEmail(),
'paymentDataId' => ($order->getPaymentData()['id'] ?? '') ?: '',
'quantity' => (string) $item->getQuantity(),
'amount' => (string) $item->getAmount(),
'summ' => (string) ($item->getAmount() * $item->getQuantity()),
'promoCodeId' => $order->getPromoCode()?->getCode() ?? '',
'utmSource' => $item->getUtmSource(),
'utmContent' => $item->getUtmContent(),
default => '',
};
}
/**
* @param Request $request
* @param EntityManagerInterface $entityManager
* @param Order|null $order
* @return Response
* @throws TransportExceptionInterface
* @Route("/backend/order/{id}/edit", name="backend_order_edit")
* @Security("is_granted('ROLE_MERCHANT') or is_granted('ROLE_CALLCENTER') or is_granted('ROLE_MANAGER') or is_granted('ROLE_CLIENT')")
*/
public function edit(Request $request, EntityManagerInterface $entityManager, Order $order = null): Response
{
if (null === $order) {
return $this->redirectToRoute('backend_order_index');
}
$form = $this->createForm(OrderType::class, $order);
$orderItemsTimeToSend = [];
foreach ($order->getOrderItems() as $orderItem) {
$orderItemsTimeToSend[$orderItem->getTiberiumOrderId()] = $orderItem->getTimeToSend();
}
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$orderId = $order->getId();
foreach($order->getOrderItems() as $orderItem){
if ($orderItemsTimeToSend[$orderItem->getTiberiumOrderId()] !== $orderItem->getTimeToSend()) {
$orderItemId = $orderItem->getTiberiumOrderId();
$client = HttpClient::create();
$client->request('GET', $_ENV['NOTIFICATION_SERVICE_API_URL'].'/sending/newTime', [
'query' => [
'orderId' => $orderId->toString(),
'orderItemId' => $orderItemId,
],
'verify_peer' => false,
'verify_host' => false,
]
);
}
}
$entityManager->persist($order);
$entityManager->flush();
return $this->redirectToRoute('backend_order_edit', ['id' => $request->get('id')]);
}
//$certificateGoals = $certificateService->getCertificateGoals($order);
$publicDir = $this->getParameter('aws.site');
return $this->render('order/edit.html.twig', [
'user' => $this->getUser(),
'certificateGoals' => [],
'order_form' => $form->createView(),
'order' => $order,
'public' => $publicDir,
'cover_dir' => '/' . $order->getWidget()->getHashedId() . $this->getParameter('app.cover_dir'),
'user_files_dir' => $this->getParameter('app.user_files_dir'),
'notificationService' => $_ENV['NOTIFICATION_SERVICE_API_URL']
]);
}
/**
* Переотправить сертификаты и отбивки по заказу
*
* @param OrderServiceInterface $orderService
* @param Order|null $order
* @return Response
* @Route("/order/{id}/send", name="order_send")
* @Security("is_granted('ROLE_MANAGER') or is_granted('ROLE_SUPERADMINISTRATOR')")
*/
public function send(OrderServiceInterface $orderService, Order $order = null): Response
{
if (!$order) {
$response['status'] = ResponseStatusEnum::error;
$response['data'] = 'OrderNotFound';
$response['message'] = 'OrderNotFound';
return $this->json($response, Response::HTTP_NOT_FOUND);
}
try {
$orderService->reSendCertificatesByOrder($order);
} catch (Throwable|Error $e) {
$response['status'] = ResponseStatusEnum::error;
$response['data'] = 'Resend Certificates Error';
$response['message'] = $e->getMessage();
return $this->json($response, Response::HTTP_UNPROCESSABLE_ENTITY);
}
$response['status'] = ResponseStatusEnum::ok;
$response['data'] = null;
$response['message'] = 'Resend Certificates Success';
return $this->json($response);
}
/**
*
*
* @param Request $request
* @param EntityManagerInterface $entityManager
* @param Order|null $order
* @return Response
* @Route("/order/{id}/remove", name="order_remove")
* @Security("is_granted('ROLE_MANAGER') or is_granted('ROLE_SUPERADMINISTRATOR')")
*/
public function remove(OrderServiceInterface $orderService, Request $request, EntityManagerInterface $entityManager, Order $order = null): Response
{
$removeOrderItemId = $request->query->get('order_item_id');
try {
$orderItems = $order->getOrderItems();
$orderService->removeOrderItem($entityManager, $orderItems, $removeOrderItemId);
} catch (Throwable|Error $e) {
$response['status'] = ResponseStatusEnum::error;
$response['data'] = 'Remove orderItem Error';
$response['message'] = $e->getMessage();
return $this->json($response, Response::HTTP_UNPROCESSABLE_ENTITY);
}
$response['status'] = ResponseStatusEnum::ok;
$response['data'] = null;
$response['message'] = 'Remove orderItem Success';
return $this->json($response);
}
/**
* @param Request $request
* @param HistorySendRepository $historySendRepository
* @return Response
* @Route("/backend/order/{id}/history", name="backend_order_history_send")
* @IsGranted("ROLE_SUPERADMINISTRATOR")
*/
public function history(Request $request, HistorySendRepository $historySendRepository): Response
{
$result = $historySendRepository->findBy(['order' => $request->get('id')]);
return $this->render('order/history.html.twig', ['history' => $result]);
}
/**
* @Route("/orderItem/{id}/receipt", name="order_get_receipt")
* @Security("is_granted('ROLE_MANAGER') or is_granted('ROLE_SUPERADMINISTRATOR')")
*/
public function getOrderReceipt(OrderItem $orderItem, OrderServiceInterface $orderService): Response
{
try {
$receipt = $orderService->getReceiptLinkByOrderItem($orderItem);
} catch (Exception|Error $e) {
$error = $e->getMessage();
}
return $this->render('order/receipt.html.twig', [
'orderItem' => $orderItem,
'error' => $error ?? null,
'receipt' => $receipt ?? null,
]);
}
/**
* Пересоздать заказ поставщику (ПЦ)
*
* @Route("/order/{id}/reorder", name="order_reorder")
* @Security("is_granted('ROLE_MANAGER') or is_granted('ROLE_SUPERADMINISTRATOR')")
*/
public function reSendOrderToVendor(OrderServiceInterface $orderService, Order $order = null): Response
{
if (!$order) {
$response['status'] = ResponseStatusEnum::error;
$response['data'] = 'OrderNotFound';
$response['message'] = 'OrderNotFound';
return $this->json($response, Response::HTTP_NOT_FOUND);
}
try {
$orderService->reSendOrderToVendor($order);
} catch (Throwable|Error $e) {
$response['status'] = ResponseStatusEnum::error;
$response['data'] = 'Reorder Error';
$response['message'] = $e->getMessage();
return $this->json($response, Response::HTTP_UNPROCESSABLE_ENTITY);
}
$response['status'] = ResponseStatusEnum::ok;
$response['data'] = null;
$response['message'] = 'Reorder Success';
return $this->json($response);
}
/**
* Пересоздать заказ поставщику (ПЦ)
*
* @Route("/order/{id}/status", name="order_status")
* @Security("is_granted('ROLE_SUPERADMINISTRATOR')")
*/
public function reStatusOrderFromVendor(OrderServiceInterface $orderService, Order $order = null): Response
{
if (!$order) {
$response['status'] = ResponseStatusEnum::error;
$response['data'] = 'OrderNotFound';
$response['message'] = 'OrderNotFound';
return $this->json($response, Response::HTTP_NOT_FOUND);
}
try {
$orderService->reStatusOrderFromVendor($order);
} catch (Throwable|Error $e) {
$response['status'] = ResponseStatusEnum::error;
$response['data'] = 'Reorder Error';
$response['message'] = $e->getMessage();
return $this->json($response, Response::HTTP_UNPROCESSABLE_ENTITY);
}
$response['status'] = ResponseStatusEnum::ok;
$response['data'] = null;
$response['message'] = 'Reorder Success';
return $this->json($response);
}
}