src/Controller/UploadSkudController.php line 19

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Entity\Skud;
  4. use App\Form\UploadSkudType;
  5. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  6. use Symfony\Component\Routing\Annotation\Route;
  7. use Symfony\Component\HttpFoundation\Request;
  8. use Doctrine\Persistence\ManagerRegistry;
  9. use Symfony\Component\HttpFoundation\File\Exception\FileException;
  10. use Psr\Log\LoggerInterface;
  11. class UploadSkudController extends AbstractController
  12. {
  13. /**
  14. * @Route("/upload", name="app_upload_skud")
  15. */
  16. public function index(Request $request, ManagerRegistry $doctrine, LoggerInterface $logger)
  17. {
  18. $skud = new Skud();
  19. $form = $this->createForm(UploadSkudType::class, $skud);
  20. $form->handleRequest($request);
  21. if ($form->isSubmitted())
  22. {
  23. $file = $form->get('file')->getData();
  24. $originalFilename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
  25. $newFilename = $originalFilename.'.'.$file->guessExtension();
  26. try
  27. {
  28. $file->move('var/upload/', $newFilename);
  29. // читаем файл
  30. $sheetData = [];
  31. $fileName = 'var/upload/' . $newFilename;
  32. $inputFileType = \PhpOffice\PhpSpreadsheet\IOFactory::identify($fileName);
  33. $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType);
  34. try {
  35. $spreadsheet = $reader->load($fileName);
  36. $sheetData = $spreadsheet->getActiveSheet()->toArray(null, true, true, true);
  37. } catch(\PhpOffice\PhpSpreadsheet\Reader\Exception $e) {
  38. $logger->error($e->getMessage());
  39. }
  40. // парсим файл
  41. $count = count($sheetData);
  42. $fact = [];
  43. if ($count > 0 &&
  44. mb_strpos($sheetData[4]['Q'], ': ') !== false &&
  45. mb_strpos($sheetData[4]['Q'], ' - ') !== false)
  46. {
  47. // Определяем учетный период:
  48. $arrTmp = explode(': ', $sheetData[4]['Q']);
  49. $arrTmp = explode(' - ', end($arrTmp));
  50. $start = date('Y-m-d', strtotime($arrTmp[0]));
  51. $end = date('Y-m-d', strtotime($arrTmp[1]));
  52. $startDate = new \DateTime($start);
  53. $endDate = new \DateTime($end);
  54. $interval = new \DateInterval('P1D');
  55. $dateRange = new \DatePeriod($startDate, $interval, $endDate->modify('+1 day'));
  56. $arrPeriod = [];
  57. foreach ($dateRange as $date) {
  58. $arrPeriod[] = $date->format('Y-m-d');
  59. }
  60. // Определяем год из учётного периода файла:
  61. $arrTmp = explode('.', $sheetData[4]['Q']);
  62. $year = end($arrTmp);
  63. // Удаляем шапку
  64. $sheetData = array_slice($sheetData, 11);
  65. $arrUcode = [];
  66. foreach ($sheetData as $row)
  67. {
  68. if (strpos($row['AD'], ':') === false) continue;
  69. $arrIn = explode(' ', $row['U']);
  70. if (!is_array($arrIn) || count($arrIn) != 2) continue;
  71. $el = [];
  72. if ($row['A'] != '')
  73. {
  74. $ucode = $row['I'];
  75. $arrUcode[] = $ucode;
  76. }
  77. $arrOut = explode(' ', $row['Y']);
  78. if (!is_array($arrOut) || count($arrOut) != 2)
  79. {
  80. $exitDate = $arrIn[0];
  81. $exitTime = '';
  82. }
  83. else
  84. {
  85. $exitDate = $arrOut[0];
  86. $exitTime = $arrOut[1];
  87. }
  88. $curDate = date('Y-m-d', strtotime($arrIn[0] . '.' . $year));
  89. $el = [
  90. 'UCODE' => $ucode,
  91. 'DATE' => $curDate,
  92. 'TIMESTAMP' => strtotime($curDate),
  93. 'ENTRY' => date('Y-m-d H:i:s', strtotime($arrIn[0] . '.' . $year . ' ' . $arrIn[1])),
  94. 'ENTRY_TIME' => (isset($arrIn[1])) ? date('H:i:s', strtotime($arrIn[1])) : '',
  95. 'EXIT' => date('Y-m-d H:i:s', strtotime($exitDate . '.' . $year . ' ' . $exitTime)),
  96. 'EXIT_TIME' => (strlen($exitTime) > 0) ? date('H:i:s', strtotime($exitTime)) : $exitTime,
  97. 'INSIDE_TIME' => (strlen($row['AD']) > 0) ? date('H:i:s', strtotime($row['AD'])) : '',
  98. 'OUTSIDE_TIME' => (strlen($row['AF']) > 0) ?date('H:i:s', strtotime($row['AF'])) : ''
  99. ];
  100. $fact[] = $el;
  101. }
  102. }
  103. $ucode = ' (' . implode(',', $arrUcode) . ') ';
  104. // обогащаем данные и сохраняем в БД
  105. if (count($fact) > 0)
  106. {
  107. // вытащим активных пользователей
  108. $query = "SELECT * FROM `user` WHERE `active` = 1";
  109. $arrUsersActive = self::queryToDb($doctrine, $query);
  110. // вытащим nrd
  111. $query = "SELECT * FROM `nrd` WHERE `ucode` in $ucode AND `period_start` <= '$start' AND `period_end` >= '$end'";
  112. $arrNrd = self::queryToDb($doctrine, $query);
  113. // вытащим графики
  114. $query = "SELECT * FROM `work_schedule` WHERE `ucode` in $ucode AND `period_start` <= '$start' AND `period_end` >= '$end'";
  115. $arrStartEnd = self::queryToDb($doctrine, $query);
  116. // вытащим дни-исключения
  117. $query = "SELECT * FROM `exception_day` WHERE `date` BETWEEN '$start' AND '$end'";
  118. $arrExceptionsDay = self::queryToDb($doctrine, $query);
  119. $nullTime = strtotime('00:00:00');
  120. $lunchStart = strtotime('12:45:00') - $nullTime;
  121. $lunchEnd = strtotime('13:33:00') - $nullTime;
  122. // Инициализируем выходной массив
  123. $allDatesFact = [];
  124. foreach ($arrUsersActive as $user) {
  125. foreach ($arrPeriod as $date) {
  126. $inPlan = strtotime('08:00:00') - $nullTime;
  127. $exitPlan = 0;
  128. $lunchBreak = strtotime('00:48:00') - $nullTime;
  129. // Если день входит в исключения, то берём длительность из исключения, иначе смотрим по дню недели
  130. $dateCol = array_column($arrExceptionsDay, 'date');
  131. $indexExDate = array_search($date, $dateCol);
  132. if ($indexExDate !== false && $arrExceptionsDay[$indexExDate]['type'] == 1) //выходной день
  133. {
  134. $dayType = 1;
  135. $dayLength = strtotime('00:00:00') - $nullTime;
  136. }
  137. elseif ($indexExDate !== false && $arrExceptionsDay[$indexExDate]['type'] == 2) // предпраздничный
  138. {
  139. $dayType = 2;
  140. $dayLength = strtotime($arrExceptionsDay[$indexExDate]['end_time']) - $inPlan - $nullTime;
  141. $exitPlan = strtotime($arrExceptionsDay[$indexExDate]['end_time']) - $nullTime;
  142. }
  143. elseif (date('N', strtotime($date)) == 5) // Пятница
  144. {
  145. $dayType = 0;
  146. $dayLength = strtotime('07:12:00') - $nullTime;
  147. $exitPlan = strtotime('16:00:00') - $nullTime;
  148. }
  149. elseif (date('N', strtotime($date)) > 5) // Суббота и воскресенье
  150. {
  151. $dayType = 1;
  152. $dayLength = strtotime('00:00:00') - $nullTime;
  153. }
  154. else
  155. {
  156. $dayType = 0;
  157. $dayLength = strtotime('08:12:00') - $nullTime;
  158. $exitPlan = strtotime('17:00:00') - $nullTime;
  159. }
  160. // Определим перерыв на обед и уникальные графики
  161. $ucode = array_column($arrStartEnd, 'ucode');
  162. $indexSEDate = array_search($user['ucode'], $ucode);
  163. if ($indexSEDate !== false && $arrStartEnd[$indexSEDate]['day'] == 0) // уникальный график
  164. {
  165. $lunchBreak = strtotime($arrStartEnd[$indexSEDate]['lunch_break']) - $nullTime;
  166. $dayLength = strtotime($arrStartEnd[$indexSEDate]['to_work']) - $nullTime;
  167. $exitPlan = strtotime($arrStartEnd[$indexSEDate]['day_end']) - $nullTime;
  168. $inPlan = strtotime($arrStartEnd[$indexSEDate]['day_start']) - $nullTime;
  169. }
  170. elseif ($indexSEDate !== false) // уникальный график по дням
  171. {
  172. $array_position = array_keys($ucode, $el['UCODE']);
  173. foreach($array_position as $key)
  174. {
  175. if (date('N', strtotime($date)) == $arrStartEnd[$key]['day'])
  176. {
  177. $lunchBreak = strtotime($arrStartEnd[$key]['lunch_break']) - $nullTime;
  178. if ($indexExDate !== false && $arrExceptionsDay[$indexExDate]['type'] == 2) // если день исключение
  179. {
  180. $exitPlan = strtotime($arrExceptionsDay[$indexExDate]['end_time']) - $nullTime;
  181. }
  182. else
  183. {
  184. $dayLength = strtotime($arrStartEnd[$key]['to_work']) - $nullTime;
  185. $exitPlan = strtotime($arrStartEnd[$key]['day_end']) - $nullTime; // особый график
  186. $inPlan = strtotime($arrStartEnd[$key]['day_start']) - $nullTime;
  187. }
  188. }
  189. }
  190. }
  191. $allDatesFact[$user['ucode']][$date] = [
  192. 'UCODE' => $user['ucode'],
  193. 'DATE_TYPE' => $dayType,
  194. 'DATE' => $date,
  195. 'ENTRY' => null,
  196. 'EXIT' => null,
  197. 'INSIDE_TIME' => null,
  198. 'NORMA' => $dayLength,
  199. 'CALCULATED' => null,
  200. 'OUTSIDE_TIME' => null,
  201. 'BALANCE' => (-1) * $dayLength,
  202. 'LATENES' => 0,
  203. 'EARLY_LEAVE' => 0,
  204. 'IN_PLAN' => $inPlan,
  205. 'LUNCH_BREAK' => $lunchBreak,
  206. 'EXIT_PLAN' => $exitPlan,
  207. ];
  208. }
  209. }
  210. // Очищаем БД
  211. foreach ($arrPeriod as $date) {
  212. $query = "DELETE FROM `fact` WHERE `date` = '" . $date . "';";
  213. self::queryToDb($doctrine, $query);
  214. }
  215. // бежим по каждой записи из файла импорта и создаём новый массив, который содержит все даты периода
  216. foreach ($fact as $el)
  217. {
  218. $flagDataError = false;
  219. if (!isset($allDatesFact[$el['UCODE']])) continue;
  220. // Вытащим подготовленные ранее данные по плановым входам / типам дней и т.д.
  221. $planned = $allDatesFact[$el['UCODE']][$el['DATE']];
  222. // Полное отработанное время. Оно содержит приход до 8 и работу в обед. Учтём это ниже.
  223. if ($el['INSIDE_TIME'] != '00:00:00')
  224. {
  225. $inside_time = strtotime($el['INSIDE_TIME']) - $nullTime;
  226. }
  227. else {
  228. $flagDataError = true; // ошибка во входящих данных - нулевое присутствие
  229. $el['BALANCE'] = (-1) * $planned['NORMA'];
  230. }
  231. $inFact = $planned['IN_PLAN'];
  232. if (strpos($el['ENTRY_TIME'], ':') !== false)
  233. {
  234. $inFact = strtotime($el['ENTRY_TIME']) - $nullTime;
  235. }
  236. $exit = $planned['EXIT_PLAN'];
  237. if (strpos($el['EXIT_TIME'], ':') !== false)
  238. {
  239. $exit = strtotime($el['EXIT_TIME']) - $nullTime;
  240. }
  241. $outside = 0;
  242. if (strpos($el['OUTSIDE_TIME'], ':') !== false)
  243. {
  244. $outside = strtotime($el['OUTSIDE_TIME']) - $nullTime;
  245. }
  246. // Вычитаем обед из времени нахождения внутри
  247. if ($exit > $lunchStart && $exit <= $lunchEnd)
  248. {
  249. // Если уход раньше позже 12:45 но раньше 13:33,
  250. // то отнимем обед из времени внутри. Ибо это обед.
  251. $inside_time -= $exit - $lunchStart;
  252. }
  253. elseif ($exit > $lunchEnd)
  254. {
  255. // Если уход позже 13:33 то отнимем весь обед.
  256. // Но мы знаем, что кто-то ходит обедать за территорию.
  257. // Это нужно учесть
  258. if ($planned['LUNCH_BREAK'] > $outside)
  259. {
  260. $inside_time -= $planned['LUNCH_BREAK'] - $outside;
  261. }
  262. }
  263. // Если пришёл до начала своего рабочего дня, считаем, что пришёл ровно. Иначе - опоздание.
  264. $diff = $planned['IN_PLAN'] - $inFact;
  265. $el['LATENES'] = 0;
  266. if (($planned['DATE_TYPE'] == 0 || $planned['DATE_TYPE'] == 2) && $diff < 0)
  267. {
  268. $el['LATENES'] = 1;
  269. }
  270. elseif ($planned['DATE_TYPE'] == 0 || $planned['DATE_TYPE'] == 2)
  271. {
  272. $inside_time -= $diff;
  273. }
  274. // Если ушёл слишком рано - ранний уход
  275. $el['EARLY_LEAVE'] = 0;
  276. if (($planned['DATE_TYPE'] == 0 || $planned['DATE_TYPE'] == 2) && (($exit - $planned['EXIT_PLAN']) < 0)) {
  277. $el['EARLY_LEAVE'] = 1;
  278. }
  279. $el['CALCULATED'] = '';
  280. $el['NORMA'] = $planned['NORMA'];
  281. if (!$flagDataError) {
  282. $el['BALANCE'] = $inside_time - $planned['NORMA'];
  283. $el['CALCULATED'] = date('H:i:s', $inside_time);
  284. }
  285. $allDatesFact[$el['UCODE']][$el['DATE']] = $el;
  286. }
  287. }
  288. // Пишем в базу данных
  289. foreach ($allDatesFact as $dates) {
  290. foreach ($dates as $el) {
  291. if (!is_null($el['ENTRY'])) {
  292. $query = "INSERT INTO `fact`
  293. (
  294. `ucode`,
  295. `date`,
  296. `entry_time`,
  297. `exit_time`,
  298. `inside_time_raw`,
  299. `norm_time`,
  300. `inside_time`,
  301. `outside_time`,
  302. `balance`,
  303. `lateness`,
  304. `early_leave`
  305. )
  306. VALUES
  307. (
  308. '". $el['UCODE'] . "',
  309. '". $el['DATE'] . "',
  310. '". $el['ENTRY'] . "',
  311. '". $el['EXIT'] . "',
  312. '". $el['INSIDE_TIME'] . "',
  313. '". date('H:i:s', $el['NORMA']) . "',
  314. '". $el['CALCULATED'] . "',
  315. '". $el['OUTSIDE_TIME'] . "',
  316. '". $el['BALANCE'] . "',
  317. '". $el['LATENES'] . "',
  318. '". $el['EARLY_LEAVE'] . "'
  319. );";
  320. self::queryToDb($doctrine, $query);
  321. }
  322. else {
  323. $query = "INSERT INTO `fact`
  324. (
  325. `ucode`,
  326. `date`,
  327. `norm_time`,
  328. `inside_time`,
  329. `balance`
  330. )
  331. VALUES
  332. (
  333. '". $el['UCODE'] . "',
  334. '". $el['DATE'] . "',
  335. '". date('H:i:s', $el['NORMA']) . "',
  336. '". $el['CALCULATED'] . "',
  337. '". $el['BALANCE'] . "'
  338. );";
  339. self::queryToDb($doctrine, $query);
  340. }
  341. }
  342. }
  343. // Удаляем файл
  344. if(file_exists($fileName)) unlink($fileName);
  345. // Редиректим на отчёт
  346. return $this->redirectToRoute('list');
  347. }
  348. catch (FileException $e)
  349. {
  350. $logger->error($e->getMessage());
  351. return $this->redirectToRoute('app_upload_skud_fail');
  352. }
  353. }
  354. return $this->render('upload/skud.html.twig', [
  355. 'form' => $form->createView(),
  356. 'controller_name' => 'UploadSkudController',
  357. ]);
  358. }
  359. private static function queryToDb($doctrine, $query)
  360. {
  361. $result = [];
  362. $conn = $doctrine->getConnection();
  363. $stmt = $conn->prepare($query);
  364. $rs = $stmt->execute();
  365. $result = $rs->fetchAllAssociative();
  366. return $result;
  367. }
  368. }