Compare commits

...

2 Commits

Author SHA1 Message Date
7984461d2c Send out only short message type 2026-01-16 14:38:55 +01:00
9845ae0c2f Send out only once per quarter 2026-01-16 14:36:36 +01:00
3 changed files with 143 additions and 11 deletions

View 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)');
}
}

View File

@ -5,6 +5,8 @@ declare(strict_types=1);
namespace App\Command;
use App\Entity\Contacts;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Uid\Uuid;
use Doctrine\ORM\EntityManagerInterface;
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';
public function __construct(
private readonly EntityManagerInterface $em,
private readonly HttpClientInterface $http
private EntityManagerInterface $em,
private readonly HttpClientInterface $http,
private readonly ManagerRegistry $doctrine,
) {
parent::__construct();
}
@ -219,7 +222,8 @@ final class CleanMobileCommand extends Command
// Create a Contact entity for DB insertion
$contact = new Contacts();
$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->setContacted(false);
$contact->setParsedFilename($inputPath);
@ -227,7 +231,10 @@ final class CleanMobileCommand extends Command
$contact->setStudyId($study_id);
$contact->setParsedFileLinenum($rowCount + 1);
$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 {
$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)
// -------------------------------------------------------------
if (\count($validContacts) > 0) {
$batchSize = 100;
$batch = [];
foreach ($validContacts as $i => $contact) {
$this->em->persist($contact);
if ((($i + 1) % $batchSize) === 0) {
$this->em->flush();
$this->em->clear(); // free memory
$batch[] = $contact;
if (count($batch) === 100) {
$this->flushBatch($batch, $io);
$batch = [];
}
}
$this->em->flush();
$this->em->clear();
if ($batch) {
$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;
}
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 elementwise');
// 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 UnitofWork 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();
}
}

View File

@ -6,6 +6,11 @@ use App\Repository\ContactsRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: ContactsRepository::class)]
#[ORM\UniqueConstraint(
name: 'unique_quartal',
columns: ['phone_number', 'due_quartal'],
options: ['where' => "due_quartal IS NOT NULL"]
)]
class Contacts
{
#[ORM\Id]
@ -19,6 +24,9 @@ class Contacts
#[ORM\Column(nullable: true)]
private ?\DateTime $due_date = null;
#[ORM\Column(nullable: true)]
private ?int $due_quartal = null;
#[ORM\Column]
private ?bool $contacted = null;
@ -184,4 +192,14 @@ class Contacts
{
$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;
}
}