fix: key participant status like the participants they belong to
This commit is contained in:
@@ -96,7 +96,7 @@ class BookingPayloadBuilder
|
||||
foreach ($bookingData->participants as $index => $participant) {
|
||||
$participantPayload = [
|
||||
'@id' => $index + 1,
|
||||
'status' => $bookingData->participantsStatus[$index],
|
||||
'status' => $bookingData->participantsStatus[$index] ?? null,
|
||||
...$participant->toPayload(),
|
||||
];
|
||||
|
||||
|
||||
@@ -49,11 +49,13 @@ class BookingParser extends AbstractParser
|
||||
|
||||
$booking->applicant = $this->parsePersonalData($node->filterXPath('//anmelder'));
|
||||
|
||||
$participantsStatus = $this->getArrayValue($node->filterXPath('//status_teilnehmer'), '/');
|
||||
$booking->participantsStatus = array_combine(range(0, count($participantsStatus) - 1), $participantsStatus);
|
||||
|
||||
$booking->participants = $this->parseParticipants($node->filterXPath('//teilnehmerliste/teilnehmer'));
|
||||
|
||||
$booking->participantsStatus = $this->mapParticipantsStatus(
|
||||
array_keys($booking->participants),
|
||||
$this->getArrayValue($node->filterXPath('//status_teilnehmer'), '/'),
|
||||
);
|
||||
|
||||
$paymentData = $node->filterXPath('//zahlung');
|
||||
$booking->paymentId = $this->getAttrOrNullValue($paymentData, 'idzahlungsart');
|
||||
$booking->paymentLabel = $this->getAttrOrNullValue($paymentData, 'bezeichnung');
|
||||
@@ -129,6 +131,38 @@ class BookingParser extends AbstractParser
|
||||
return $participants;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the positional status_teilnehmer list onto the participant keys.
|
||||
*
|
||||
* BusPro sends the statuses as a slash-separated list in teilnehmerliste order and
|
||||
* without ids, while participants are keyed by "BusPro participant id - 1" (see
|
||||
* parseParticipants()). Zipping the two in document order keeps both arrays on the same
|
||||
* keys even when the ids are not contiguous from 1, which BookingPayloadBuilder relies
|
||||
* on when it reads participantsStatus[$index] while iterating participants, and which
|
||||
* gates editing in ParticipantController::isParticipantCanceled().
|
||||
*
|
||||
* Surplus entries on either side are dropped rather than shifting the remaining ones
|
||||
* onto the wrong participant.
|
||||
*
|
||||
* @param list<int> $participantKeys Participant array keys, in document order
|
||||
* @param list<string> $statusValues Status codes, in document order
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function mapParticipantsStatus(array $participantKeys, array $statusValues): array
|
||||
{
|
||||
$count = min(count($participantKeys), count($statusValues));
|
||||
|
||||
if (0 === $count) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_combine(
|
||||
array_slice($participantKeys, 0, $count),
|
||||
array_slice($statusValues, 0, $count),
|
||||
);
|
||||
}
|
||||
|
||||
private function parsePersonalData(Crawler $node): PersonalData
|
||||
{
|
||||
$personalData = new PersonalData();
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\BusProNet\XmlParser;
|
||||
|
||||
use App\BusProNet\XmlCrawlerFactory;
|
||||
use App\BusProNet\XmlParser\BookingParser;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests that participantsStatus stays aligned with the participants array.
|
||||
*
|
||||
* Participants are keyed by "BusPro participant id - 1" while status_teilnehmer is an
|
||||
* id-less, slash-separated list in document order. BookingPayloadBuilder reads
|
||||
* participantsStatus[$index] while iterating participants, and
|
||||
* ParticipantController::isParticipantCanceled() gates editing on it, so the two arrays
|
||||
* must share their keys even when BusPro's ids are not contiguous from 1.
|
||||
*/
|
||||
class BookingParserParticipantsStatusTest extends TestCase
|
||||
{
|
||||
private BookingParser $parser;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->parser = new BookingParser();
|
||||
}
|
||||
|
||||
public function testStatusesAreKeyedLikeParticipantsForContiguousIds(): void
|
||||
{
|
||||
$booking = $this->parse('F/S/F', [1, 2, 3]);
|
||||
|
||||
$this->assertSame([0, 1, 2], array_keys($booking->participants));
|
||||
$this->assertSame([0 => 'F', 1 => 'S', 2 => 'F'], $booking->participantsStatus);
|
||||
}
|
||||
|
||||
public function testStatusesFollowParticipantIdsWhenIdsAreNotContiguous(): void
|
||||
{
|
||||
$booking = $this->parse('F/S/F', [1, 4, 7]);
|
||||
|
||||
$this->assertSame([0, 3, 6], array_keys($booking->participants));
|
||||
$this->assertSame([0 => 'F', 3 => 'S', 6 => 'F'], $booking->participantsStatus);
|
||||
|
||||
// Every participant the payload builder iterates must find its own status.
|
||||
foreach ($booking->participants as $index => $participant) {
|
||||
$this->assertArrayHasKey($index, $booking->participantsStatus);
|
||||
}
|
||||
}
|
||||
|
||||
public function testSurplusStatusEntriesAreDroppedInsteadOfShifting(): void
|
||||
{
|
||||
$booking = $this->parse('F/S/F/F', [1, 2, 3]);
|
||||
|
||||
$this->assertSame([0 => 'F', 1 => 'S', 2 => 'F'], $booking->participantsStatus);
|
||||
}
|
||||
|
||||
public function testMissingStatusEntriesLeaveTheRemainingParticipantsAligned(): void
|
||||
{
|
||||
$booking = $this->parse('F/S', [1, 2, 3]);
|
||||
|
||||
$this->assertSame([0 => 'F', 1 => 'S'], $booking->participantsStatus);
|
||||
$this->assertArrayNotHasKey(2, $booking->participantsStatus);
|
||||
}
|
||||
|
||||
public function testStatusListSurroundedByWhitespaceIsParsed(): void
|
||||
{
|
||||
// BusPro pretty-prints the element, so the text node carries newlines and indentation.
|
||||
$booking = $this->parse("\n F/S/F\n ", [1, 2, 3]);
|
||||
|
||||
$this->assertSame([0 => 'F', 1 => 'S', 2 => 'F'], $booking->participantsStatus);
|
||||
}
|
||||
|
||||
public function testEmptyStatusListDoesNotFail(): void
|
||||
{
|
||||
$booking = $this->parse('', [1, 2]);
|
||||
|
||||
$this->assertSame([], $booking->participantsStatus);
|
||||
$this->assertCount(2, $booking->participants);
|
||||
}
|
||||
|
||||
/** @param list<int> $participantIds */
|
||||
private function parse(string $statusList, array $participantIds): \App\BusProNet\Model\Booking
|
||||
{
|
||||
$participants = '';
|
||||
foreach ($participantIds as $id) {
|
||||
$participants .= sprintf(
|
||||
'<teilnehmer id="%d"><name>Teilnehmer %1$d</name><vorname>Test</vorname></teilnehmer>',
|
||||
$id,
|
||||
);
|
||||
}
|
||||
|
||||
$xml = sprintf(
|
||||
'<?xml version="1.0" encoding="utf-8"?>
|
||||
<ergebnis>
|
||||
<satz typ="KUNDENKONTO" />
|
||||
<art>Vorgang_Details</art>
|
||||
<idbuchung>98787</idbuchung>
|
||||
<status_teilnehmer>%s</status_teilnehmer>
|
||||
<zahlung idzahlungsart="1" bezeichnung="Überweisung" art="U" />
|
||||
<teilnehmerliste>%s</teilnehmerliste>
|
||||
</ergebnis>',
|
||||
$statusList,
|
||||
$participants,
|
||||
);
|
||||
|
||||
return $this->parser->parse(XmlCrawlerFactory::create($xml)->filterXPath('//ergebnis'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user