src/AppBundle/Services/ImportService.php line 96

Open in your IDE?
  1. <?php
  2. namespace App\AppBundle\Services;
  3. use App\AppBundle\Annotations\Frontend;
  4. use App\AppBundle\Annotations\Import;
  5. use App\AppBundle\Annotations\Validation;
  6. use App\AppBundle\Entity\ImportData;
  7. use App\AppBundle\Entity\MvMycmMyamsCustomer;
  8. use App\AppBundle\Entity\MvMycmMyamsDealerGroup;
  9. use App\AppBundle\Entity\PSD_Action;
  10. use App\AppBundle\Entity\PSD_Customer;
  11. use App\AppBundle\Entity\PSD_Customer_Park;
  12. use App\AppBundle\Entity\PSD_Customer_Park_Import;
  13. use App\AppBundle\Entity\PSD_Customer_Type;
  14. use App\AppBundle\Entity\PSD_Sellout;
  15. use App\AppBundle\Entity\PSD_Vehicle_Type;
  16. use App\AppBundle\Entity\PSD_Visit_Frequency;
  17. use App\AppBundle\Entity\Sellout;
  18. use App\AppBundle\Entity\User;
  19. use App\AppBundle\Entity\UserImportHeader;
  20. use App\AppBundle\Entity\UserImportSettings;
  21. use App\AppBundle\Other\Constants;
  22. use App\AppBundle\Repository\ImportDataRepository;
  23. use DateTime;
  24. use DateTimeZone;
  25. use App\AppBundle\Repository\UserRepository;
  26. use Coolshop\CoolJobBundle\Entity\JobLog;
  27. use Coolshop\CoolJobBundle\Service\CommandAdapter;
  28. use Doctrine\Common\Annotations\AnnotationReader;
  29. use Doctrine\Common\Annotations\AnnotationRegistry;
  30. use Doctrine\Common\Collections\ArrayCollection;
  31. use Doctrine\ORM\AbstractQuery;
  32. use Doctrine\ORM\EntityManager;
  33. use Doctrine\ORM\EntityManagerInterface;
  34. use Doctrine\ORM\OptimisticLockException;
  35. use Doctrine\ORM\ORMException;
  36. use Doctrine\ORM\PersistentCollection;
  37. use Doctrine\ORM\Query;
  38. use Doctrine\Persistence\Mapping\MappingException;
  39. use Exception;
  40. use HttpException;
  41. use ReflectionClass;
  42. use ReflectionException;
  43. use ReflectionProperty;
  44. use Symfony\Component\Console\Input\InputInterface;
  45. use Symfony\Component\Console\Output\OutputInterface;
  46. use Symfony\Component\Console\Style\SymfonyStyle;
  47. use Symfony\Component\DependencyInjection\Container;
  48. use Symfony\Component\DependencyInjection\ContainerInterface;
  49. use Symfony\Component\HttpFoundation\JsonResponse;
  50. use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
  51. use Symfony\Component\HttpKernel\Exception\HttpException as ExceptionHttpException;
  52. use Symfony\Component\Messenger\MessageBusInterface;
  53. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
  54. ini_set('memory_limit', '2048M');
  55. class ImportService {
  56. private array $metadata = [];
  57. protected ?SymfonyStyle $io = null;
  58. protected EntityManager $em;
  59. private ImportData $importData;
  60. private string $type;
  61. private array $customers = [];
  62. private array $annotations = [];
  63. private $customerRepo;
  64. private array $cachedOptions = [];
  65. private Container $container;
  66. private $translator;
  67. const CLASS_TYPES = array(
  68. ImportData::TYPE_CUSTOMER => PSD_Customer::class,
  69. ImportData::TYPE_SELLOUT => PSD_Sellout::class,
  70. ImportData::TYPE_CUSTOMER_PARK => PSD_Customer_Park_Import::class,
  71. );
  72. public function __construct(
  73. EntityManagerInterface $em,
  74. ContainerInterface $container,
  75. private MessageBusInterface $messageBus,
  76. ) {
  77. $this->em = $em;
  78. $this->annotations[] = Import::class;
  79. $this->container = $container;
  80. $this->customerRepo = $em->getRepository(PSD_Customer::class);
  81. $this->translator = $this->container->get('translator');
  82. }
  83. public function setIO(InputInterface $input, OutputInterface $output)
  84. {
  85. $this->io = new SymfonyStyle($input, $output);
  86. }
  87. public function loadImportData(int $id, User $user, string $type):ImportService {
  88. return $this->setImportData($this->em->getRepository(ImportData::class)->findOneBy([
  89. "id" => $id,
  90. "type" => $type,
  91. "createdBy" => $user,
  92. ]));
  93. }
  94. public function setImportData(ImportData $importData):ImportService {
  95. $this->importData = $importData;
  96. $this->type = $importData->getType();
  97. return $this;
  98. }
  99. public function setType(string $type):ImportService {
  100. $this->type = $type;
  101. return $this;
  102. }
  103. /**
  104. * @throws OptimisticLockException
  105. * @throws ORMException
  106. * @throws MappingException
  107. * @throws Exception
  108. */
  109. public function import($importDataId)
  110. {
  111. $this->io->writeln([
  112. "ImportData: #$importDataId",
  113. "Start",
  114. ]);
  115. $importDataRepository = $this->em->getRepository(ImportData::class);
  116. $this->setImportData($importDataRepository->find($importDataId));
  117. switch($this->type)
  118. {
  119. case ImportData::TYPE_CUSTOMER:
  120. $this->importCustomer();
  121. break;
  122. case ImportData::TYPE_SELLOUT:
  123. $this->importSellout();
  124. break;
  125. case ImportData::TYPE_CUSTOMER_PARK:
  126. $this->importCustomerPark();
  127. break;
  128. }
  129. $this->io->writeln("End");
  130. }
  131. /**
  132. * @throws OptimisticLockException
  133. * @throws ORMException
  134. * @throws MappingException
  135. * @throws Exception
  136. */
  137. private function importCustomer() {
  138. $this->io->writeln("Type: customer");
  139. $this->io->writeln("Start at: ".$this->getDateTimeNow()->format("d/m/Y H:i:s"));
  140. $customerRepository = $this->em->getRepository(PSD_Customer::class);
  141. $data = $this->importData->getData();
  142. $metadata = array_filter($this->getMetadata(), function ($value) { return !is_null($value["extra"]); });
  143. $dmsCodes = array_column($data, 'dmsCode');
  144. $info = $this->em
  145. ->getRepository(MvMycmMyamsDealerGroup::class)
  146. ->findOneBy(["cdDealerGroup" => $this->importData->getDealerGroup()]);
  147. $isConnected = isset($info) && $info->getCdConnected();
  148. $customers = $customerRepository->findByDealerGroupAndDmsCodes($this->importData->getDealerGroup(),$dmsCodes,$isConnected);
  149. foreach($data as $i => $row) {
  150. $customer = $customers[$isConnected ? sprintf('%08s', $row["dmsCode"]) : $row["dmsCode"]] ?? null;
  151. if(is_null($customer)) {
  152. $customer = PSD_Customer::fromArray([
  153. "dmsCode" => $row["dmsCode"],
  154. "dealerGroup" => $this->importData->getDealerGroup(),
  155. "isConnected" => $isConnected
  156. ]);
  157. }
  158. $row["dmsCode"] = $isConnected ? sprintf('%08s', $row["dmsCode"]) : $row["dmsCode"];
  159. // handle mappedBy attribute (any way to do it better??)
  160. $row["dealerCode"] = $row["dmsDealerBranch"];
  161. unset($row["dmsDealerBranch"]);
  162. $customer->setFromArray($row);
  163. $this->em->persist($customer);
  164. $this->em->flush();
  165. if($i && $i%50==0) {
  166. $this->io->writeln("Insert: ".$i);
  167. gc_collect_cycles();
  168. }
  169. $row = null;
  170. $customer = null;
  171. }
  172. // if(in_array($_ENV['APP_ENV'], ['qua', 'dev'])){
  173. // $message = new CommandAdapter('cool:psd_customer:process',['dealerships' => [$this->importData->getDealerGroup()]],User::class, '-1');
  174. // $this->messageBus->dispatch($message);
  175. // }
  176. $this->em->flush();
  177. $this->em->clear();
  178. $this->io->writeln("Insert: ".count($data));
  179. $this->io->writeln("End at: ".$this->getDateTimeNow()->format("d/n/Y H:i:s"));
  180. }
  181. /**
  182. * @throws OptimisticLockException
  183. * @throws ORMException
  184. * @throws MappingException
  185. * @throws Exception
  186. */
  187. private function importCustomerPark(){
  188. $this->io->writeln("Type: customer parks");
  189. $this->io->writeln("Start at: ".$this->getDateTimeNow()->format("d/m/Y H:i:s"));
  190. $data = $this->importData->getData();
  191. $customerRepository = $this->em->getRepository(PSD_Customer::class);
  192. $metadata = array_filter($this->getMetadata(), function ($value) { return !is_null($value["extra"]); });
  193. $dmsCodes = array_column($data, 'dmsCode');
  194. $info = $this->em
  195. ->getRepository(MvMycmMyamsDealerGroup::class)
  196. ->findOneBy(["cdDealerGroup" => $this->importData->getDealerGroup()]);
  197. $isConnected = isset($info) && $info->getCdConnected();
  198. $customers = $customerRepository->findByDealerGroupAndDmsCodes($this->importData->getDealerGroup(),$dmsCodes,$isConnected);
  199. $customerParkRepository = $this->em->getRepository(PSD_Customer_Park::class);
  200. $customersIds = array_reduce($customers, function($acc, $c) {
  201. $acc[] = $c->getId();
  202. return $acc;
  203. }) ?? array();
  204. $customerParks = $customerParkRepository->findOneByCustomerAndVehicleTypeAndCurrentYear($customersIds);
  205. $currentCustomerParks = empty($customerParks) ? $customerParkRepository->getCurrentCustomerParkByCustomerIds($customersIds) : null;
  206. unset($customersIds);
  207. $this->io->writeln("Customer Park fetched ".$this->getDateTimeNow()->format("d/m/Y H:i:s"));
  208. $vehicleTypes = [];
  209. foreach ($metadata as $value){
  210. $vehicleTypes[$value["extra"]] = $this->em->getReference(PSD_Vehicle_Type::class, $value["extra"]);
  211. }
  212. $newVehicles = 0;
  213. $modifiedVehicles = 0;
  214. foreach($data as $row) {
  215. $customer = $customers[$isConnected ? sprintf('%08s', $row["dmsCode"]) : $row["dmsCode"]] ?? null;
  216. foreach ($metadata as $key => $value) {
  217. $customerPark = isset($customers) ? $customerParks[$customer->getId()."_".$value["extra"]] ?? null : null;
  218. if(isset($row[$key]) && $row[$key] != ""){
  219. if(is_null($customerPark)) {
  220. $this->insertOrUpdateCustomerPark(true,$customerPark,$customer,$vehicleTypes[$value["extra"]],$row[$key]);
  221. $newVehicles++;
  222. }else{
  223. $this->insertOrUpdateCustomerPark(false,$customerPark,$customer,$vehicleTypes[$value["extra"]],$row[$key]);
  224. $modifiedVehicles++;
  225. }
  226. }else if(isset($row[$key])) {
  227. $currentPark = isset($customers) ? $currentCustomerParks[$customer->getId()."_".$value["extra"]] ?? null : null;
  228. if(is_null($currentPark) && is_null($customerPark)) {
  229. $this->insertOrUpdateCustomerPark(true,$currentPark,$customer,$vehicleTypes[$value["extra"]],0);
  230. $newVehicles++;
  231. }else {
  232. $currentPark
  233. ? $this->insertOrUpdateCustomerPark(true,$currentPark,$customer,$vehicleTypes[$value["extra"]],$currentPark->getNumberOfvehicles())
  234. : $this->insertOrUpdateCustomerPark(false,$customerPark,$customer,$vehicleTypes[$value["extra"]],$customerPark->getNumberOfvehicles());
  235. $modifiedVehicles++;
  236. }
  237. }
  238. unset($row[$key]);
  239. }
  240. }
  241. $this->em->flush();
  242. $this->em->clear();
  243. $this->io->writeln("Insert ".$newVehicles." new Vehicles");
  244. $this->io->writeln("Modified ".$modifiedVehicles." Vehicles");
  245. $this->io->writeln("End at: ".$this->getDateTimeNow()->format("d/n/Y H:i:s"));
  246. }
  247. private function insertOrUpdateCustomerPark($isInsert, $customerPark, $customer, $vehicleType, $numberOfVehicle = null){
  248. if($isInsert){
  249. $customerPark = PSD_Customer_Park::fromArray([
  250. "customer" => $customer,
  251. "vehicleType" => $vehicleType,
  252. "year" => date("Y"),
  253. "numberOfVehicles" => $numberOfVehicle,
  254. ]);
  255. }else{
  256. $customerPark->setFromArray(["numberOfVehicles" => $numberOfVehicle]);
  257. }
  258. $this->em->persist($customerPark);
  259. }
  260. private function addCustomer(string $customerDmsCode):ImportService {
  261. $this->customers[$customerDmsCode] = $this->customers[$customerDmsCode] ?? null;
  262. return $this;
  263. }
  264. private function syncCustomers():ImportService {
  265. $this->io->writeln("sync customers");
  266. $customerRepo = $this->em->getRepository(PSD_Customer::class);
  267. $customerDmsCodes = [];
  268. $customer = null;
  269. $customerCode = null;
  270. foreach($this->customers as $customerCode => $customer) {
  271. if(is_null($customer)) {
  272. $customerDmsCodes[] = $customerCode;
  273. }else{
  274. $this->em->persist($customer);
  275. }
  276. }
  277. if(count($customerDmsCodes) > 0) {
  278. $this->io->writeln("load ".count($customerDmsCodes)." customers");
  279. foreach($customerRepo->findBy(["dmsCode" => $customerDmsCodes]) as $customer) {
  280. $this->customers[$customer->getDmsCode()] = $customer;
  281. }
  282. }
  283. foreach($this->customers as $customerCode => $customer) {
  284. if(is_null($customer)) {
  285. $this->em->persist($this->customers[$customerCode] = PSD_Customer::fromArray([
  286. "dmsCode" => $customerCode,
  287. "dealerGroup" => $this->importData->getDealerGroup(),
  288. ]));
  289. }
  290. }
  291. // $this->em->flush();
  292. unset($customerRepo);
  293. unset($customerDmsCodes);
  294. unset($customer);
  295. unset($customerCode);
  296. return $this;
  297. }
  298. private function getCustomer(string $customerDmsCode):PSD_Customer {
  299. if(isset($this->customers[$customerDmsCode]) && !is_null($this->customers[$customerDmsCode])) return $this->customers[$customerDmsCode];
  300. throw new Exception("Customer not loaded: $customerDmsCode");
  301. }
  302. /**
  303. * @throws ORMException
  304. * @throws MappingException
  305. * @throws OptimisticLockException
  306. */
  307. private function importSellout() {
  308. $this->io->writeln("Type: sellout");
  309. $this->io->writeln("Start at: ".$this->getDateTimeNow()->format("d/m/Y H:i:s"));
  310. $selloutRepository = $this->em->getRepository(PSD_Sellout::class);
  311. $customerRepository = $this->em->getRepository(PSD_Customer::class);
  312. $data = $this->importData->getData();
  313. $startAt = $this->getDateTimeNow();
  314. $dates = [];
  315. $this->io->writeln("Data validation...");
  316. foreach($this->validateRows() as $indexRow => $row) {
  317. foreach($row as $column => $error) {
  318. if($error !== "") throw new Exception("Data not valid: #$indexRow ($column)");
  319. }
  320. }
  321. $this->io->writeln("Validated");
  322. foreach($data as $i => $row) {
  323. $data[$i] = $this->remapProperties($row);
  324. }
  325. $this->em->getConfiguration()->setSQLLogger(null);
  326. // $dmsCodes = [];
  327. foreach($data as $i => $row) {
  328. // if (in_array($row["customerDmsCode"], $dmsCodes)) {
  329. // continue;
  330. // } else {
  331. // $dmsCodes[] = $row["customerDmsCode"];
  332. // }
  333. $customer = $customerRepository->findOneByDmsCodeAndDealerGroup($row["customerDmsCode"], $this->importData->getDealerGroup());
  334. if(is_null($customer)) {
  335. $customer = PSD_Customer::fromArray([
  336. "dmsCode" => $row["customerDmsCode"],
  337. "dealerGroup" => $this->importData->getDealerGroup(),
  338. ]);
  339. $this->em->persist($customer);
  340. }
  341. // handle dms code
  342. $row["cdCustomer"] = $row["customerDmsCode"];
  343. unset($row["customerDmsCode"]);
  344. $row["customer"] = $customer;
  345. $row["totalInvoicedValue"] = floatval(str_replace(',', '.', (string) $row['totalInvoicedValue']));
  346. $row["invoiceDate"] = $this->getDateTime("d/m/Y", $row["invoiceDate"]);
  347. $row["year"] = (int) $row["invoiceDate"]->format('Y');
  348. $row["month"] = (int) $row["invoiceDate"]->format('m');
  349. $row["dealerGroup"] = $this->importData->getDealerGroup();
  350. // fill codes
  351. $row["channel"] = strtoupper($row["channelCode"]);
  352. $row["cdChannel"] = strtoupper($row["channelCode"]);
  353. unset($row["channelCode"]);
  354. $row["cdWarranty"] = "Z"; //$this->getWarrantyCode($row["warranty"]);
  355. $dates[$row["invoiceDate"]->format("Ym")] = $row["invoiceDate"];
  356. $this->em->persist(PSD_Sellout::fromArray($row));
  357. $this->em->flush();
  358. $this->em->clear();
  359. if($i && $i%1000==0) {
  360. $this->io->writeln("Insert: ".$i);
  361. }
  362. $data[$i] = null;
  363. $i = null;
  364. $row = null;
  365. $customer = null;
  366. gc_collect_cycles();
  367. }
  368. $this->io->writeln("Insert: ".count($data));
  369. // if(in_array($_ENV['APP_ENV'], ['qua', 'dev'])){
  370. // $message = new CommandAdapter('cool:psd_customer:process',['dealerships' => [$this->importData->getDealerGroup()]],User::class, '-1');
  371. // $this->messageBus->dispatch($message);
  372. // }
  373. $this->em->flush();
  374. $this->em->clear();
  375. $this->io->writeln("Delete months...");
  376. foreach($selloutRepository->deleteBefore($startAt, $dates, $this->importData->getDealerGroup()) as $result) {
  377. $this->io->writeln($result[0]->format("m/Y")." [".($result[1]?"v":" ")."]");
  378. }
  379. $this->io->writeln("End at: ".$this->getDateTimeNow()->format("d/n/Y H:i:s"));
  380. }
  381. function memory($size) {
  382. return @round($size/pow(1024,floor(log($size,1024))),2).' '.["b","kb","mb","gb","tb","pb"][floor(log($size,1024))];
  383. }
  384. private function getDateTimeNow():DateTime {
  385. return $this->getDateTime("U.u", sprintf('%.6F', microtime(true)));
  386. }
  387. private function getDateTime(string $format, string $datetime):DateTime {
  388. $datetimeReturn = DateTime::createFromFormat($format, $datetime);
  389. $datetimeReturn->setTimezone(new DateTimeZone(date_default_timezone_get()));
  390. return $datetimeReturn;
  391. }
  392. public function getColumns($user) {
  393. $userImportHeaderRepository = $this->em->getRepository(UserImportHeader::class);
  394. $metadata = array_values($this->getMetadata());
  395. $indexedHeaders = $userImportHeaderRepository->findByUserTypeIndexed($user, $this->type);
  396. return array_map(function ($property) use($indexedHeaders) {
  397. $property["userFieldName"] = isset($indexedHeaders[$property["fieldName"]])? $indexedHeaders[$property["fieldName"]]["headerUser"]: "";
  398. return $property;
  399. }, $metadata);
  400. }
  401. public function getIndexedUserColumns($user) {
  402. $columns = [];
  403. foreach($this->getColumns($user) as $column) {
  404. if($column["userFieldName"] != "") {
  405. $columns[$column["userFieldName"]] = $column["fieldName"];
  406. }
  407. }
  408. return $columns;
  409. }
  410. public function addAnnotation(string $annotation):self {
  411. $this->metadata = [];
  412. if(!in_array($annotation, $this->annotations)) $this->annotations[] = $annotation;
  413. return $this;
  414. }
  415. public function removeAnnotation(string $annotation):self {
  416. $this->annotations = array_filter($this->annotations, function($item) use($annotation) {
  417. return $item != $annotation;
  418. });
  419. return $this;
  420. }
  421. public function getMetadata(array $filters = [], ?string $fieldMap = null):array {
  422. $meta = $this->calculateMetadata(self::CLASS_TYPES[$this->type], $filters, $fieldMap);
  423. return $meta;
  424. }
  425. public function calculateMetadata($class, array $filters = [], ?string $fieldMap = null):array {
  426. $metadata = [];
  427. if(!empty($this->metadata[$class])) {
  428. $metadata = $this->metadata[$class];
  429. }else{
  430. $classMetadata = $this->em->getClassMetadata($class);
  431. $reader = new AnnotationReader();
  432. $reflectionClass = $classMetadata->getReflectionClass();
  433. foreach($this->annotations as $annotation) {
  434. $reflection = $reader->getClassAnnotation($reflectionClass, $annotation) ?? new $annotation();
  435. $metadata = $reflection->decorateMetadata($metadata, $this->em, $class);
  436. }
  437. $this->metadata[$class] = $metadata;
  438. }
  439. if(!empty($filters)) {
  440. foreach($metadata as $fieldName => $meta) {
  441. foreach($filters as $key => $value) {
  442. if($meta[$key] !== $value) {
  443. unset($metadata[$fieldName]);
  444. break;
  445. }
  446. }
  447. }
  448. }
  449. uasort($metadata , function($m1, $m2) {
  450. return $m1["position"] > $m2["position"];
  451. });
  452. if(!is_null($fieldMap)) {
  453. $metadata = array_values(array_map(function($meta) use($fieldMap) {
  454. return $meta[$fieldMap];
  455. }, $metadata));
  456. }
  457. return $metadata;
  458. }
  459. public function existsInternalColumn($internal) {
  460. return isset($this->getMetadata()[$internal]);
  461. }
  462. public function convertToInternalColumns() {
  463. $columns = $this->getIndexedUserColumns($this->importData->getCreatedBy());
  464. $data = $this->importData->getData();
  465. $columnNames = array_keys($columns);
  466. foreach($data as $index => $row) {
  467. foreach($row as $key => $value) {
  468. if(in_array($key, $columnNames)) {
  469. $data[$index][$columns[$key]] = $value;
  470. }
  471. unset($data[$index][$key]);
  472. }
  473. }
  474. $this->importData->setData($data);
  475. return $this->importData;
  476. }
  477. private function getIndexData(array $keys, array $row) {
  478. return implode("", array_map(function($key) use($row) {
  479. return $row[$key];
  480. }, $keys));
  481. }
  482. public function validateRows($isEdit = null): array {
  483. $errors = [];
  484. $rows = $this->importData->getData();
  485. $metadata = [];
  486. if(count($rows) > 0){
  487. foreach ($rows[0] as $column => $value){
  488. $metadata[$column] = $this->getColumnMetadata($column);
  489. }
  490. }
  491. foreach($rows as $id => $row) {
  492. $errColumns = [];
  493. foreach($row as $column => $value) {
  494. $errColumns[$column] = $this->testColumn($column, $value, $metadata[$column], $id, $isEdit);
  495. }
  496. $errors[$id] = $errColumns;
  497. }
  498. return $errors;
  499. }
  500. /**
  501. * @throws Exception
  502. */
  503. public function testColumn(string $column, $value, $metadata = null, $indexRow = null, $isEdit = false):string {
  504. if($column == "dmsCode" && $this->importData->getType() == ImportData::TYPE_CUSTOMER_PARK && !$this->customerRepo->checkCustomerExists($this->importData->getDealerGroup(),$value))
  505. {
  506. if(!$isEdit)
  507. {
  508. $row = $indexRow + 2;
  509. throw new BadRequestHttpException("Provided dmsCode on line #$row ($column) is not related to any customer");
  510. }
  511. return "There is no customer with this dmsCode";
  512. }
  513. if(!isset($metadata))
  514. $metadata = $this->getColumnMetadata($column);
  515. if(isset($metadata["extra"]) && $this->importData->getType() == ImportData::TYPE_CUSTOMER_PARK){
  516. return preg_match("/^(\d+)?$/", $value) !== 1 ? "Not castable to target type." : "";
  517. }
  518. if(($metadata["unique"] ?? false) && !$this->isUnique($column, $value)) return "Field not unique";
  519. if($value === "" && !$metadata["nullable"] && !$metadata["hasDefault"]) return "Empty field not nullable";
  520. if($value !== "" && !empty($metadata["regex"]) && preg_match($metadata["regex"], $value) !== 1) {
  521. if($metadata["label"])
  522. return $this->translator->trans($metadata["label"]);
  523. return "Did not pass validation";
  524. }
  525. switch($metadata["type"]) {
  526. case "text":
  527. $metadata["type"] = "string";
  528. case "string":
  529. if(!is_null($metadata["length"]) && strlen($value) > $metadata["length"]) return "Too long.";
  530. break;
  531. case "datetime":
  532. // todo: get format from table
  533. if(preg_match("/^((0[1-9]|[12]\d|3[01])\/(0[1-9]|1[0-2])\/(\d{4}))*$/", $value) !== 1) return "Not castable to target type.";
  534. break;
  535. case "boolean":
  536. if(preg_match("/^[01]?$/", $value) !== 1) return "Not castable to target type.";
  537. break;
  538. case "integer":
  539. case "int":
  540. if(preg_match("/^(-?\d+)?$/", $value) !== 1) return "Not castable to target type.";
  541. if($value !== "" && is_null($metadata["extra"]) && $metadata["options"] && !in_array(intval($value), $this->findOptions($metadata["options"]))) return "Not in options.";
  542. break;
  543. case "float":
  544. if(preg_match("/^(-?\d+([,.]\d+)?)?$/", $value) !== 1) return "Not castable to target type.";
  545. break;
  546. default:
  547. if(!settype($value, $metadata["type"])) return "Not castable to target type.";
  548. break;
  549. }
  550. return "";
  551. }
  552. public function setCell(int $row, string $column, $value):ImportData {
  553. $data = $this->importData->getData();
  554. $data[$row][$column] = $value;
  555. $this->importData->setData($data);
  556. return $this->importData;
  557. }
  558. public function getImportData():ImportData {
  559. return $this->importData;
  560. }
  561. public function removeRow($row):void {
  562. $data = $this->importData->getData();
  563. unset($data[$row]);
  564. $this->importData->setData($data);
  565. }
  566. public function removeErrors() {
  567. $data = $this->importData->getData();
  568. $errors = $this->validateRows(true);
  569. foreach($errors as $row => $columns) {
  570. foreach($columns as $error) {
  571. if($error !== "") {
  572. unset($data[$row]);
  573. break;
  574. }
  575. }
  576. }
  577. $this->importData->setData($data);
  578. }
  579. private function getColumnMetadata(string $column):array {
  580. if(isset($this->getMetadata()[$column])) return $this->getMetadata()[$column];
  581. foreach($this->getMetadata() as $meta) {
  582. if($meta["mappedBy"] === $column) return $meta;
  583. }
  584. throw new ExceptionHttpException("Column not found: $column");
  585. }
  586. private function remapProperties($row) {
  587. $result = [];
  588. foreach($row as $column => $meta) {
  589. $metadata = $this->getColumnMetadata($column);
  590. $result[$metadata["mappedBy"]] = $meta;
  591. }
  592. return $result;
  593. }
  594. public function getHeaders():array {
  595. $headers = [];
  596. $metadata = $this->getMetadata();
  597. usort($metadata , function($h1, $h2) {
  598. return $h1["position"] > $h2["position"];
  599. });
  600. foreach($metadata as $meta) {
  601. if($meta["exclude"]) continue;
  602. $headers[] = $meta["mappedBy"];
  603. }
  604. return $headers;
  605. }
  606. public function getIncludedMetadata():array {
  607. $metadata = [];
  608. foreach($this->getMetadata() as $key => $meta) {
  609. if($meta["exclude"]) continue;
  610. $metadata[$key] = $meta;
  611. }
  612. return $metadata;
  613. }
  614. public function getExposeName():string {
  615. $reader = new AnnotationReader();
  616. $classReflection = new ReflectionClass(self::CLASS_TYPES[$this->type]);
  617. $frontendAnnotation = $reader->getClassAnnotation($classReflection, Frontend::class) ?? new Frontend();
  618. return $frontendAnnotation->getExposeAs();
  619. }
  620. public function removeFrontendColumns():ImportData {
  621. $data = $this->importData->getData();
  622. foreach($data as $row => $rowData) {
  623. foreach($rowData as $column => $value) {
  624. if($this->isFrontendColumn($column)) {
  625. unset($data[$row][$column]);
  626. }
  627. }
  628. }
  629. return $this->importData->setData($data);
  630. }
  631. public function isFrontendColumn(string $column):bool {
  632. return $this->getColumnMetadata($column)["privacy"] === Frontend::PRIVACY_FRONTEND;
  633. }
  634. private function findOptions($options):array {
  635. $optionsKey = implode($options, "-");
  636. if(isset($this->cachedOptions[$optionsKey])) return $this->cachedOptions[$optionsKey];
  637. if($options) if(class_exists($options[0])) {
  638. $repository = $this->em->getRepository($options[0]);
  639. if(isset($options[1])) $this->cachedOptions[$optionsKey] = $repository->{$options[1]}();
  640. else $this->cachedOptions[$optionsKey] = array_column($repository->createQueryBuilder("e")->select("e.id")->getQuery()->getScalarResult(), "id");
  641. } else if(is_callable($options[0])) {
  642. $this->cachedOptions[$optionsKey] = $options[0]();
  643. } else if(defined($options[0])) {
  644. $this->cachedOptions[$optionsKey] = constant($options[0]);
  645. }
  646. return $this->cachedOptions[$optionsKey];
  647. }
  648. private function isUnique(string $column, $value):bool {
  649. $count = 0;
  650. foreach($this->getImportData()->getData() as $row) {
  651. if($row[$column] == $value) $count++;
  652. if($count > 1) return false;
  653. }
  654. return true;
  655. }
  656. private function getChannelCode(string $channel) : string
  657. {
  658. return array_key_exists($channel, Constants::CHANNELS) ? Constants::CHANNELS[$channel] : "X";
  659. }
  660. private function getWarrantyCode(string $warranty) : string
  661. {
  662. return array_key_exists($warranty, Constants::WARRANTIES) ? Constants::WARRANTIES[$warranty] : "X";
  663. }
  664. }