Compare commits
2 Commits
5c1db6f948
...
7984461d2c
| Author | SHA1 | Date | |
|---|---|---|---|
| 7984461d2c | |||
| 9845ae0c2f |
34
migrations/Version20260116104840.php
Normal file
34
migrations/Version20260116104840.php
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20260116104840 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('DROP INDEX unique_quartal');
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX unique_quartal ON contacts (phone_number, due_quartal) WHERE due_quartal IS NOT NULL');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('CREATE SCHEMA public');
|
||||||
|
$this->addSql('DROP INDEX unique_quartal');
|
||||||
|
$this->addSql('CREATE INDEX unique_quartal ON contacts (phone_number, due_quartal) WHERE (due_quartal IS NOT NULL)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,8 @@ declare(strict_types=1);
|
||||||
namespace App\Command;
|
namespace App\Command;
|
||||||
|
|
||||||
use App\Entity\Contacts;
|
use App\Entity\Contacts;
|
||||||
|
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
use Symfony\Component\Uid\Uuid;
|
use Symfony\Component\Uid\Uuid;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use League\Csv\Reader;
|
use League\Csv\Reader;
|
||||||
|
|
@ -33,8 +35,9 @@ final class CleanMobileCommand extends Command
|
||||||
private string $backendApiURL = 'https://umfragetool.ukbonn.de/api/api/participant/create-qm-befr-participant';
|
private string $backendApiURL = 'https://umfragetool.ukbonn.de/api/api/participant/create-qm-befr-participant';
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly EntityManagerInterface $em,
|
private EntityManagerInterface $em,
|
||||||
private readonly HttpClientInterface $http
|
private readonly HttpClientInterface $http,
|
||||||
|
private readonly ManagerRegistry $doctrine,
|
||||||
) {
|
) {
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
@ -219,7 +222,8 @@ final class CleanMobileCommand extends Command
|
||||||
// Create a Contact entity for DB insertion
|
// Create a Contact entity for DB insertion
|
||||||
$contact = new Contacts();
|
$contact = new Contacts();
|
||||||
$contact->setPhoneNumber($row['HANDY_E164']);
|
$contact->setPhoneNumber($row['HANDY_E164']);
|
||||||
$dueDate = (new \DateTime('today'))->setTime(12, 0, 0);
|
$today = new \DateTime('today');
|
||||||
|
$dueDate = ($today)->setTime(12, 0, 0);
|
||||||
$contact->setDueDate($dueDate);
|
$contact->setDueDate($dueDate);
|
||||||
$contact->setContacted(false);
|
$contact->setContacted(false);
|
||||||
$contact->setParsedFilename($inputPath);
|
$contact->setParsedFilename($inputPath);
|
||||||
|
|
@ -227,7 +231,10 @@ final class CleanMobileCommand extends Command
|
||||||
$contact->setStudyId($study_id);
|
$contact->setStudyId($study_id);
|
||||||
$contact->setParsedFileLinenum($rowCount + 1);
|
$contact->setParsedFileLinenum($rowCount + 1);
|
||||||
$contact->setParsedFileLine($sanitiseUtf8(implode(';', $row)));
|
$contact->setParsedFileLine($sanitiseUtf8(implode(';', $row)));
|
||||||
$contact->setMsgContentType($rowCount % 2 ? 1 : 2);
|
// $contact->setMsgContentType($rowCount % 2 ? 1 : 2); // alternativ lang oder kurz
|
||||||
|
$contact->setMsgContentType(2); // nur kurzformat
|
||||||
|
$quartal = $today->format('Y') . ceil($today->format('n') / 3);
|
||||||
|
$contact->setDueQuartal((int) $quartal);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$result = $this->http->request('POST', $this->backendApiURL . "/" . $study_id . "/" . $study_id_chain, [
|
$result = $this->http->request('POST', $this->backendApiURL . "/" . $study_id . "/" . $study_id_chain, [
|
||||||
|
|
@ -254,16 +261,29 @@ final class CleanMobileCommand extends Command
|
||||||
// 6️⃣ Persist the valid contacts (batch insert)
|
// 6️⃣ Persist the valid contacts (batch insert)
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
if (\count($validContacts) > 0) {
|
if (\count($validContacts) > 0) {
|
||||||
$batchSize = 100;
|
$batch = [];
|
||||||
|
|
||||||
foreach ($validContacts as $i => $contact) {
|
foreach ($validContacts as $i => $contact) {
|
||||||
$this->em->persist($contact);
|
$batch[] = $contact;
|
||||||
if ((($i + 1) % $batchSize) === 0) {
|
if (count($batch) === 100) {
|
||||||
$this->em->flush();
|
$this->flushBatch($batch, $io);
|
||||||
$this->em->clear(); // free memory
|
$batch = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->em->flush();
|
if ($batch) {
|
||||||
$this->em->clear();
|
$this->flushBatch($batch, $io);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// foreach ($validContacts as $i => $contact) {
|
||||||
|
// $this->em->persist($contact);
|
||||||
|
// try {
|
||||||
|
// $this->em->flush();
|
||||||
|
// } catch(UniqueConstraintViolationException $e) {
|
||||||
|
// $io->warning(['The number ', $contact->getPhoneNumber(), ' already contacted for quartal', $contact->getDueQuartal()]);
|
||||||
|
// }
|
||||||
|
// $this->em->clear();
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
|
|
@ -280,4 +300,64 @@ final class CleanMobileCommand extends Command
|
||||||
|
|
||||||
return Command::SUCCESS;
|
return Command::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function flushBatch(array $batch, $io): void
|
||||||
|
{
|
||||||
|
$this->em = $this->resetEntityManager();
|
||||||
|
foreach ($batch as $c) {
|
||||||
|
$this->em->persist($c);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->em->flush(); // versucht, den kompletten Batch zu speichern
|
||||||
|
} catch (UniqueConstraintViolationException $e) {
|
||||||
|
$io->warning('Batch conflict – falling back to element‑wise');
|
||||||
|
|
||||||
|
// Transaction rollback (falls aktiv)
|
||||||
|
$conn = $this->em->getConnection();
|
||||||
|
if ($conn->isTransactionActive()) {
|
||||||
|
$conn->rollBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Der EM ist jetzt *geschlossen* → resetten
|
||||||
|
if (!$this->em->isOpen()) {
|
||||||
|
$this->em = $this->resetEntityManager(); // hol dir einen frischen EM
|
||||||
|
}
|
||||||
|
|
||||||
|
// Einzelweise weiter versuchen, damit die „guten“ Zeilen nicht verloren gehen
|
||||||
|
$this->flushElementsIndividually($batch, $io);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batch erfolgreich – Speicher freigeben
|
||||||
|
$this->em->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function flushElementsIndividually(array $contacts, $io): void
|
||||||
|
{
|
||||||
|
$io->warning('flushing individually');
|
||||||
|
foreach ($contacts as $contact) {
|
||||||
|
$this->em->persist($contact);
|
||||||
|
$io->warning('flushing individually2');
|
||||||
|
try {
|
||||||
|
$this->em->flush();
|
||||||
|
$io->warning('flushing individually3');
|
||||||
|
} catch (UniqueConstraintViolationException $e) {
|
||||||
|
$io->warning(['The number ', $contact->getPhoneNumber(), ' already contacted for quartal', $contact->getDueQuartal()]);
|
||||||
|
// das duplizierte Objekt aus dem Unit‑of‑Work entfernen
|
||||||
|
$this->em->detach($contact);
|
||||||
|
$io->warning('flushing individually4');
|
||||||
|
if (!$this->em->isOpen()) {
|
||||||
|
$this->em = $this->resetEntityManager(); // hol dir einen frischen EM
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// jedes Entity einzeln freigeben, sonst wächst der Speicher
|
||||||
|
$this->em->clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resetEntityManager(): EntityManagerInterface
|
||||||
|
{
|
||||||
|
return $this->doctrine->resetManager();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,11 @@ use App\Repository\ContactsRepository;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: ContactsRepository::class)]
|
#[ORM\Entity(repositoryClass: ContactsRepository::class)]
|
||||||
|
#[ORM\UniqueConstraint(
|
||||||
|
name: 'unique_quartal',
|
||||||
|
columns: ['phone_number', 'due_quartal'],
|
||||||
|
options: ['where' => "due_quartal IS NOT NULL"]
|
||||||
|
)]
|
||||||
class Contacts
|
class Contacts
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
|
|
@ -19,6 +24,9 @@ class Contacts
|
||||||
#[ORM\Column(nullable: true)]
|
#[ORM\Column(nullable: true)]
|
||||||
private ?\DateTime $due_date = null;
|
private ?\DateTime $due_date = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?int $due_quartal = null;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private ?bool $contacted = null;
|
private ?bool $contacted = null;
|
||||||
|
|
||||||
|
|
@ -184,4 +192,14 @@ class Contacts
|
||||||
{
|
{
|
||||||
$this->study_id_short = $study_id_short;
|
$this->study_id_short = $study_id_short;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getDueQuartal(): ?int
|
||||||
|
{
|
||||||
|
return $this->due_quartal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDueQuartal(?int $due_quartal): void
|
||||||
|
{
|
||||||
|
$this->due_quartal = $due_quartal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user