<?php
namespace App\Controller;
use App\Entity\Skud;
use App\Form\UploadSkudType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Psr\Log\LoggerInterface;
class UploadSkudController extends AbstractController
{
/**
* @Route("/upload", name="app_upload_skud")
*/
public function index(Request $request, ManagerRegistry $doctrine, LoggerInterface $logger)
{
$skud = new Skud();
$form = $this->createForm(UploadSkudType::class, $skud);
$form->handleRequest($request);
if ($form->isSubmitted())
{
$file = $form->get('file')->getData();
$originalFilename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
$newFilename = $originalFilename.'.'.$file->guessExtension();
try
{
$file->move('var/upload/', $newFilename);
// читаем файл
$sheetData = [];
$fileName = 'var/upload/' . $newFilename;
$inputFileType = \PhpOffice\PhpSpreadsheet\IOFactory::identify($fileName);
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType);
try {
$spreadsheet = $reader->load($fileName);
$sheetData = $spreadsheet->getActiveSheet()->toArray(null, true, true, true);
} catch(\PhpOffice\PhpSpreadsheet\Reader\Exception $e) {
$logger->error($e->getMessage());
}
// парсим файл
$count = count($sheetData);
$fact = [];
if ($count > 0 &&
mb_strpos($sheetData[4]['Q'], ': ') !== false &&
mb_strpos($sheetData[4]['Q'], ' - ') !== false)
{
// Определяем учетный период:
$arrTmp = explode(': ', $sheetData[4]['Q']);
$arrTmp = explode(' - ', end($arrTmp));
$start = date('Y-m-d', strtotime($arrTmp[0]));
$end = date('Y-m-d', strtotime($arrTmp[1]));
$startDate = new \DateTime($start);
$endDate = new \DateTime($end);
$interval = new \DateInterval('P1D');
$dateRange = new \DatePeriod($startDate, $interval, $endDate->modify('+1 day'));
$arrPeriod = [];
foreach ($dateRange as $date) {
$arrPeriod[] = $date->format('Y-m-d');
}
// Определяем год из учётного периода файла:
$arrTmp = explode('.', $sheetData[4]['Q']);
$year = end($arrTmp);
// Удаляем шапку
$sheetData = array_slice($sheetData, 11);
$arrUcode = [];
foreach ($sheetData as $row)
{
if (strpos($row['AD'], ':') === false) continue;
$arrIn = explode(' ', $row['U']);
if (!is_array($arrIn) || count($arrIn) != 2) continue;
$el = [];
if ($row['A'] != '')
{
$ucode = $row['I'];
$arrUcode[] = $ucode;
}
$arrOut = explode(' ', $row['Y']);
if (!is_array($arrOut) || count($arrOut) != 2)
{
$exitDate = $arrIn[0];
$exitTime = '';
}
else
{
$exitDate = $arrOut[0];
$exitTime = $arrOut[1];
}
$curDate = date('Y-m-d', strtotime($arrIn[0] . '.' . $year));
$el = [
'UCODE' => $ucode,
'DATE' => $curDate,
'TIMESTAMP' => strtotime($curDate),
'ENTRY' => date('Y-m-d H:i:s', strtotime($arrIn[0] . '.' . $year . ' ' . $arrIn[1])),
'ENTRY_TIME' => (isset($arrIn[1])) ? date('H:i:s', strtotime($arrIn[1])) : '',
'EXIT' => date('Y-m-d H:i:s', strtotime($exitDate . '.' . $year . ' ' . $exitTime)),
'EXIT_TIME' => (strlen($exitTime) > 0) ? date('H:i:s', strtotime($exitTime)) : $exitTime,
'INSIDE_TIME' => (strlen($row['AD']) > 0) ? date('H:i:s', strtotime($row['AD'])) : '',
'OUTSIDE_TIME' => (strlen($row['AF']) > 0) ?date('H:i:s', strtotime($row['AF'])) : ''
];
$fact[] = $el;
}
}
$ucode = ' (' . implode(',', $arrUcode) . ') ';
// обогащаем данные и сохраняем в БД
if (count($fact) > 0)
{
// вытащим активных пользователей
$query = "SELECT * FROM `user` WHERE `active` = 1";
$arrUsersActive = self::queryToDb($doctrine, $query);
// вытащим nrd
$query = "SELECT * FROM `nrd` WHERE `ucode` in $ucode AND `period_start` <= '$start' AND `period_end` >= '$end'";
$arrNrd = self::queryToDb($doctrine, $query);
// вытащим графики
$query = "SELECT * FROM `work_schedule` WHERE `ucode` in $ucode AND `period_start` <= '$start' AND `period_end` >= '$end'";
$arrStartEnd = self::queryToDb($doctrine, $query);
// вытащим дни-исключения
$query = "SELECT * FROM `exception_day` WHERE `date` BETWEEN '$start' AND '$end'";
$arrExceptionsDay = self::queryToDb($doctrine, $query);
$nullTime = strtotime('00:00:00');
$lunchStart = strtotime('12:45:00') - $nullTime;
$lunchEnd = strtotime('13:33:00') - $nullTime;
// Инициализируем выходной массив
$allDatesFact = [];
foreach ($arrUsersActive as $user) {
foreach ($arrPeriod as $date) {
$inPlan = strtotime('08:00:00') - $nullTime;
$exitPlan = 0;
$lunchBreak = strtotime('00:48:00') - $nullTime;
// Если день входит в исключения, то берём длительность из исключения, иначе смотрим по дню недели
$dateCol = array_column($arrExceptionsDay, 'date');
$indexExDate = array_search($date, $dateCol);
if ($indexExDate !== false && $arrExceptionsDay[$indexExDate]['type'] == 1) //выходной день
{
$dayType = 1;
$dayLength = strtotime('00:00:00') - $nullTime;
}
elseif ($indexExDate !== false && $arrExceptionsDay[$indexExDate]['type'] == 2) // предпраздничный
{
$dayType = 2;
$dayLength = strtotime($arrExceptionsDay[$indexExDate]['end_time']) - $inPlan - $nullTime;
$exitPlan = strtotime($arrExceptionsDay[$indexExDate]['end_time']) - $nullTime;
}
elseif (date('N', strtotime($date)) == 5) // Пятница
{
$dayType = 0;
$dayLength = strtotime('07:12:00') - $nullTime;
$exitPlan = strtotime('16:00:00') - $nullTime;
}
elseif (date('N', strtotime($date)) > 5) // Суббота и воскресенье
{
$dayType = 1;
$dayLength = strtotime('00:00:00') - $nullTime;
}
else
{
$dayType = 0;
$dayLength = strtotime('08:12:00') - $nullTime;
$exitPlan = strtotime('17:00:00') - $nullTime;
}
// Определим перерыв на обед и уникальные графики
$ucode = array_column($arrStartEnd, 'ucode');
$indexSEDate = array_search($user['ucode'], $ucode);
if ($indexSEDate !== false && $arrStartEnd[$indexSEDate]['day'] == 0) // уникальный график
{
$lunchBreak = strtotime($arrStartEnd[$indexSEDate]['lunch_break']) - $nullTime;
$dayLength = strtotime($arrStartEnd[$indexSEDate]['to_work']) - $nullTime;
$exitPlan = strtotime($arrStartEnd[$indexSEDate]['day_end']) - $nullTime;
$inPlan = strtotime($arrStartEnd[$indexSEDate]['day_start']) - $nullTime;
}
elseif ($indexSEDate !== false) // уникальный график по дням
{
$array_position = array_keys($ucode, $el['UCODE']);
foreach($array_position as $key)
{
if (date('N', strtotime($date)) == $arrStartEnd[$key]['day'])
{
$lunchBreak = strtotime($arrStartEnd[$key]['lunch_break']) - $nullTime;
if ($indexExDate !== false && $arrExceptionsDay[$indexExDate]['type'] == 2) // если день исключение
{
$exitPlan = strtotime($arrExceptionsDay[$indexExDate]['end_time']) - $nullTime;
}
else
{
$dayLength = strtotime($arrStartEnd[$key]['to_work']) - $nullTime;
$exitPlan = strtotime($arrStartEnd[$key]['day_end']) - $nullTime; // особый график
$inPlan = strtotime($arrStartEnd[$key]['day_start']) - $nullTime;
}
}
}
}
$allDatesFact[$user['ucode']][$date] = [
'UCODE' => $user['ucode'],
'DATE_TYPE' => $dayType,
'DATE' => $date,
'ENTRY' => null,
'EXIT' => null,
'INSIDE_TIME' => null,
'NORMA' => $dayLength,
'CALCULATED' => null,
'OUTSIDE_TIME' => null,
'BALANCE' => (-1) * $dayLength,
'LATENES' => 0,
'EARLY_LEAVE' => 0,
'IN_PLAN' => $inPlan,
'LUNCH_BREAK' => $lunchBreak,
'EXIT_PLAN' => $exitPlan,
];
}
}
// Очищаем БД
foreach ($arrPeriod as $date) {
$query = "DELETE FROM `fact` WHERE `date` = '" . $date . "';";
self::queryToDb($doctrine, $query);
}
// бежим по каждой записи из файла импорта и создаём новый массив, который содержит все даты периода
foreach ($fact as $el)
{
$flagDataError = false;
if (!isset($allDatesFact[$el['UCODE']])) continue;
// Вытащим подготовленные ранее данные по плановым входам / типам дней и т.д.
$planned = $allDatesFact[$el['UCODE']][$el['DATE']];
// Полное отработанное время. Оно содержит приход до 8 и работу в обед. Учтём это ниже.
if ($el['INSIDE_TIME'] != '00:00:00')
{
$inside_time = strtotime($el['INSIDE_TIME']) - $nullTime;
}
else {
$flagDataError = true; // ошибка во входящих данных - нулевое присутствие
$el['BALANCE'] = (-1) * $planned['NORMA'];
}
$inFact = $planned['IN_PLAN'];
if (strpos($el['ENTRY_TIME'], ':') !== false)
{
$inFact = strtotime($el['ENTRY_TIME']) - $nullTime;
}
$exit = $planned['EXIT_PLAN'];
if (strpos($el['EXIT_TIME'], ':') !== false)
{
$exit = strtotime($el['EXIT_TIME']) - $nullTime;
}
$outside = 0;
if (strpos($el['OUTSIDE_TIME'], ':') !== false)
{
$outside = strtotime($el['OUTSIDE_TIME']) - $nullTime;
}
// Вычитаем обед из времени нахождения внутри
if ($exit > $lunchStart && $exit <= $lunchEnd)
{
// Если уход раньше позже 12:45 но раньше 13:33,
// то отнимем обед из времени внутри. Ибо это обед.
$inside_time -= $exit - $lunchStart;
}
elseif ($exit > $lunchEnd)
{
// Если уход позже 13:33 то отнимем весь обед.
// Но мы знаем, что кто-то ходит обедать за территорию.
// Это нужно учесть
if ($planned['LUNCH_BREAK'] > $outside)
{
$inside_time -= $planned['LUNCH_BREAK'] - $outside;
}
}
// Если пришёл до начала своего рабочего дня, считаем, что пришёл ровно. Иначе - опоздание.
$diff = $planned['IN_PLAN'] - $inFact;
$el['LATENES'] = 0;
if (($planned['DATE_TYPE'] == 0 || $planned['DATE_TYPE'] == 2) && $diff < 0)
{
$el['LATENES'] = 1;
}
elseif ($planned['DATE_TYPE'] == 0 || $planned['DATE_TYPE'] == 2)
{
$inside_time -= $diff;
}
// Если ушёл слишком рано - ранний уход
$el['EARLY_LEAVE'] = 0;
if (($planned['DATE_TYPE'] == 0 || $planned['DATE_TYPE'] == 2) && (($exit - $planned['EXIT_PLAN']) < 0)) {
$el['EARLY_LEAVE'] = 1;
}
$el['CALCULATED'] = '';
$el['NORMA'] = $planned['NORMA'];
if (!$flagDataError) {
$el['BALANCE'] = $inside_time - $planned['NORMA'];
$el['CALCULATED'] = date('H:i:s', $inside_time);
}
$allDatesFact[$el['UCODE']][$el['DATE']] = $el;
}
}
// Пишем в базу данных
foreach ($allDatesFact as $dates) {
foreach ($dates as $el) {
if (!is_null($el['ENTRY'])) {
$query = "INSERT INTO `fact`
(
`ucode`,
`date`,
`entry_time`,
`exit_time`,
`inside_time_raw`,
`norm_time`,
`inside_time`,
`outside_time`,
`balance`,
`lateness`,
`early_leave`
)
VALUES
(
'". $el['UCODE'] . "',
'". $el['DATE'] . "',
'". $el['ENTRY'] . "',
'". $el['EXIT'] . "',
'". $el['INSIDE_TIME'] . "',
'". date('H:i:s', $el['NORMA']) . "',
'". $el['CALCULATED'] . "',
'". $el['OUTSIDE_TIME'] . "',
'". $el['BALANCE'] . "',
'". $el['LATENES'] . "',
'". $el['EARLY_LEAVE'] . "'
);";
self::queryToDb($doctrine, $query);
}
else {
$query = "INSERT INTO `fact`
(
`ucode`,
`date`,
`norm_time`,
`inside_time`,
`balance`
)
VALUES
(
'". $el['UCODE'] . "',
'". $el['DATE'] . "',
'". date('H:i:s', $el['NORMA']) . "',
'". $el['CALCULATED'] . "',
'". $el['BALANCE'] . "'
);";
self::queryToDb($doctrine, $query);
}
}
}
// Удаляем файл
if(file_exists($fileName)) unlink($fileName);
// Редиректим на отчёт
return $this->redirectToRoute('list');
}
catch (FileException $e)
{
$logger->error($e->getMessage());
return $this->redirectToRoute('app_upload_skud_fail');
}
}
return $this->render('upload/skud.html.twig', [
'form' => $form->createView(),
'controller_name' => 'UploadSkudController',
]);
}
private static function queryToDb($doctrine, $query)
{
$result = [];
$conn = $doctrine->getConnection();
$stmt = $conn->prepare($query);
$rs = $stmt->execute();
$result = $rs->fetchAllAssociative();
return $result;
}
}