docs: drop code references to local plan files
This commit is contained in:
1 parent
2a554bb7bf
commit
96d49fc488
15 files changed
+33
-40
No files matched your search
@@ -1,7 +1,7 @@
|
||||
# Enabled for #[MapRequestPayload] on the api controllers - nothing else in this application
|
||||
# serializes. ALLOW_EXTRA_ATTRIBUTES stays at its default (true) on purpose: the TYPO3 form
|
||||
# payload grows keys without a release here, and rejecting an unknown one would turn every
|
||||
# later mapping change into a 400 (see .ddev/plans/application-form-endpoint.md).
|
||||
# later mapping change into a 400.
|
||||
framework:
|
||||
serializer:
|
||||
name_converter: ~
|
||||
@@ -9,8 +9,7 @@ use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* What it takes to provision a teamer from a received application: the applicant's gender, and the
|
||||
* outcome of each of the three provisioning writes
|
||||
* (.ddev/plans/application-form-teamer-provisioning.md).
|
||||
* outcome of each of the three provisioning writes.
|
||||
*
|
||||
* Emptying the table first is deliberate. gender is NOT NULL because BusPro refuses an address
|
||||
* without one and Teamer::$gender is not nullable either, and the existing rows predate the
|
||||
|
||||
@@ -146,8 +146,9 @@ class ApiClient
|
||||
* "login" switches the password check off, which only works when the request identifies the
|
||||
* person by id: both ids are required, and the person id is "idadresseperson" on the way in
|
||||
* although the response calls it "idperson". The element order and the single-selection
|
||||
* payload are what BusPro was verified to accept - the art is undocumented, see
|
||||
* .ddev/plans/buspro-crm-selection-write.md before changing the shape.
|
||||
* payload are what BusPro accepted when verified against production (2026-09-25, selection
|
||||
* 1070 toggled off and back on) - the art is undocumented, so re-verify before changing the
|
||||
* shape.
|
||||
*
|
||||
* Idempotent: setting a selection that is already set answers änderung=True and changes
|
||||
* nothing, so a caller may write unconditionally and take isUpdated() as the success check.
|
||||
|
||||
@@ -54,7 +54,7 @@ class ResponseParser
|
||||
// to be routed to createCrmAttributesResponse(), which walks a
|
||||
// "selektionsmerkmale" node that is absent here and would have returned an
|
||||
// empty response with every role flag false - dead code until something
|
||||
// sent the art (.ddev/plans/buspro-crm-selection-write.md).
|
||||
// sent the art.
|
||||
return $this->createProfileUpdateResponse($xml);
|
||||
case 'Adresse_Neu':
|
||||
return $this->createRegistrationResponse($xml);
|
||||
|
||||
@@ -21,8 +21,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
*
|
||||
* The visitor waits for this response inside their own form submission and TYPO3 gives up after
|
||||
* a few seconds without retrying, so this stores the row and answers - notification mail and any
|
||||
* matching against known people belong in a worker, not here
|
||||
* (.ddev/plans/MYEP-APPLICATION-ENDPOINT.md §6).
|
||||
* matching against known people belong in a worker, not here.
|
||||
*/
|
||||
class ApplicationFormController extends AbstractController
|
||||
{
|
||||
@@ -58,7 +57,7 @@ class ApplicationFormController extends AbstractController
|
||||
'formLabel' => $submission->getFormLabel(),
|
||||
'pageUid' => $submission->getPageUid(),
|
||||
'submittedAt' => $submission->getSubmittedAt()->format(\DateTimeInterface::ATOM),
|
||||
// the labels and the answers are personal data (§7), their number is not
|
||||
// the labels and the answers are personal data, their number is not
|
||||
'detailCount' => count($submission->getDetails()),
|
||||
'receiptSent' => $receiptSent,
|
||||
]);
|
||||
|
||||
@@ -61,7 +61,7 @@ class ApplicationFormSubmissionMailer
|
||||
if (true !== $submission->isEu()) {
|
||||
$this->logger->info('Application form receipt mail skipped, not eligible', [
|
||||
'uuid' => $submission->getUuid(),
|
||||
// The value, not the applicant's nationality: a flag the form set (§7).
|
||||
// The value, not the applicant's nationality: a flag the form set.
|
||||
'euFieldPresent' => null !== $submission->isEu(),
|
||||
]);
|
||||
|
||||
|
||||
@@ -32,8 +32,7 @@ use Symfony\Component\Uid\Uuid;
|
||||
*
|
||||
* The typed columns are the mapped core agreed with the TYPO3 side. "details" is everything
|
||||
* else the visitor filled in, keyed by the German field label in the order the form asked -
|
||||
* display only, never a schema, never queried on (see
|
||||
* .ddev/plans/MYEP-APPLICATION-ENDPOINT.md §3.3).
|
||||
* display only, never a schema, never queried on.
|
||||
*/
|
||||
#[ORM\Entity(repositoryClass: ApplicationFormSubmissionRepository::class)]
|
||||
// What a list and a dedupe check ask for: the submissions of one form, newest first.
|
||||
@@ -129,7 +128,7 @@ class ApplicationFormSubmission implements TimestampableEntityInterface
|
||||
* of the payload - fromDto() must not touch them.
|
||||
*
|
||||
* The mails carry a time because a send time is a machine fact and stays one once the
|
||||
* notification mail of §6 is automated. A held call does not: the reviewer records it
|
||||
* notification mail is automated. A held call does not: the reviewer records it
|
||||
* afterwards and knows the day, so storing an invented time would only look precise.
|
||||
*/
|
||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)]
|
||||
@@ -175,7 +174,7 @@ class ApplicationFormSubmission implements TimestampableEntityInterface
|
||||
/**
|
||||
* Everything the visitor filled in beyond the mapped core, keyed by the German field label.
|
||||
* Display only: the labels are staff-written and change on a reword, so nothing may query
|
||||
* this, build a column from it or migrate old rows to a new spelling (§3.3).
|
||||
* this, build a column from it or migrate old rows to a new spelling.
|
||||
*
|
||||
* The key order is whatever the engine hands back and is not part of what this carries: on
|
||||
* MariaDB (dev and production) JSON is an alias for LONGTEXT and the document survives
|
||||
@@ -189,7 +188,7 @@ class ApplicationFormSubmission implements TimestampableEntityInterface
|
||||
|
||||
/**
|
||||
* The request body as received. Mapped keys this application does not know yet are ignored
|
||||
* on the way in (§3.2) and the TYPO3 side widens a form's mapping without a release here,
|
||||
* on the way in and the TYPO3 side widens a form's mapping without a release here,
|
||||
* so without this copy those answers would be lost for good instead of backfillable.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
@@ -214,7 +213,7 @@ class ApplicationFormSubmission implements TimestampableEntityInterface
|
||||
*
|
||||
* The raw payload is passed in separately because the DTO no longer has it: the resolver
|
||||
* drops every key the DTO has no property for, and those are the mapped fields the TYPO3
|
||||
* side has added and this application does not know yet (§3.2). Keeping the body is what
|
||||
* side has added and this application does not know yet. Keeping the body is what
|
||||
* makes them backfillable instead of lost.
|
||||
*
|
||||
* @param array<string, mixed> $payload
|
||||
|
||||
@@ -8,8 +8,8 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
/**
|
||||
* One submitted TYPO3 form, mapped from the request body by #[MapRequestPayload].
|
||||
*
|
||||
* Two rules from the contract (.ddev/plans/MYEP-APPLICATION-ENDPOINT.md §3.2, §3.3) shape this
|
||||
* class and must survive any rewrite:
|
||||
* Two rules from the contract with the TYPO3 side shape this class and must survive any
|
||||
* rewrite:
|
||||
*
|
||||
* - **Unknown keys are ignored, never rejected.** The mapped vocabulary grows on the TYPO3
|
||||
* side without a release here, so nothing may switch the serializer's ALLOW_EXTRA_ATTRIBUTES
|
||||
@@ -21,8 +21,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
* Vorname, Geschlecht, EMail, PLZ, Straße) - a payload without those cannot become a teamer at
|
||||
* all, and asking a reviewer to retype them from the answers would be worse than refusing the
|
||||
* payload. The endpoint only ever receives application forms, so this holds for every payload and
|
||||
* there is no form-type discriminator
|
||||
* (.ddev/plans/application-form-teamer-provisioning.md).
|
||||
* there is no form-type discriminator.
|
||||
*
|
||||
* Everything beyond the nine - phone, mobile, nationality, bank details - stays optional and is
|
||||
* filled in by the teamer themselves afterwards, so an absent key there still means "not part of
|
||||
@@ -334,7 +333,7 @@ class ApplicationFormSubmissionDto
|
||||
/**
|
||||
* The blob as it is stored: numbers arrive as strings from the form editor but nothing stops
|
||||
* a mapping from sending one unquoted, and an empty answer is dropped rather than kept -
|
||||
* TYPO3 strips those before sending (§3.3), so a label present in the blob always has
|
||||
* TYPO3 strips those before sending, so a label present in the blob always has
|
||||
* content. The received order is the form's order and is never touched.
|
||||
*
|
||||
* @return array<string, string|bool|list<string>>
|
||||
|
||||
@@ -12,7 +12,7 @@ use App\Service\Common\PickupOptions;
|
||||
/**
|
||||
* Turns a staffing matrix row into the draft assignments for one travel date.
|
||||
*
|
||||
* The rules, agreed 2026-10-15 (see .ddev/plans/teamer-auto-assignments-plan.md):
|
||||
* The rules, agreed with the stakeholders on 2026-10-15:
|
||||
* - one assignment per role, the role's head count as its places;
|
||||
* - without split, each ski level is a Kursleitung of its own, one place;
|
||||
* - with split, a Reiseleitung takes over each level through the combined profile, one place
|
||||
|
||||
@@ -35,9 +35,8 @@ use Psr\Log\LoggerInterface;
|
||||
* address is never created twice, and the selection write is idempotent by design.
|
||||
*
|
||||
* Nothing here reads the applicant's profile back from BusPro. Adressdaten needs the customer's
|
||||
* password, which provisioning deliberately never has (see
|
||||
* .ddev/plans/application-form-teamer-provisioning.md), so the local records are built from the
|
||||
* data that was sent plus the ids that came back.
|
||||
* password, which provisioning deliberately never has, so the local records are built from the data that
|
||||
* was sent plus the ids that came back.
|
||||
*
|
||||
* This is also the one place in the application that grants a teamer their CRM marker rather than
|
||||
* reading it. Everything else stays read-only: roles and hotel codes are synced from BusPro on
|
||||
|
||||
@@ -194,8 +194,7 @@ class ResponseParserTest extends TestCase
|
||||
/**
|
||||
* The write answers idadresse + idperson + änderung, not a selection tree - the shape
|
||||
* createProfileUpdateResponse() reads. It used to be routed to the CRM attribute parser, which
|
||||
* would have returned an empty response with every role flag false
|
||||
* (.ddev/plans/buspro-crm-selection-write.md).
|
||||
* would have returned an empty response with every role flag false.
|
||||
*/
|
||||
public function testParseCrmSelectionWriteResponse(): void
|
||||
{
|
||||
|
||||
@@ -14,8 +14,8 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
/**
|
||||
* Everything around the provisioning click that can be exercised without writing to BusPro.
|
||||
*
|
||||
* The write itself cannot: the teamer CRM selection is only changeable in the production database
|
||||
* (.ddev/plans/buspro-crm-selection-write.md), and a POST that gets as far as the api client would
|
||||
* The write itself cannot: the teamer CRM selection is only changeable in the production database,
|
||||
* and a POST that gets as far as the api client would
|
||||
* open a socket to it from a test. TeamerProvisionerTest covers the flow against a mocked client;
|
||||
* what is left here is the route, the confirmation and the one POST that is safe to make - a
|
||||
* submission TeamerProvisioner refuses before it sends anything.
|
||||
|
||||
@@ -13,8 +13,8 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* The cases here are the ones the TYPO3 side actually produces
|
||||
* (.ddev/plans/MYEP-APPLICATION-ENDPOINT.md §8): labels with slashes and umlauts, a long textarea
|
||||
* The cases here are the ones the TYPO3 side actually produces:
|
||||
* labels with slashes and umlauts, a long textarea
|
||||
* answer, a duplicate label, and a mapped key this application does not know - which must not be a
|
||||
* 400, or a later mapping change on the website silently stops every submission of that form.
|
||||
*
|
||||
@@ -22,8 +22,7 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
* birthDate, email, gender, eu, street, zipCode and city, and every one of them is a 400 when it is
|
||||
* missing. That is
|
||||
* deliberately harsh - a 400 costs a whole application - and each rejection case is pinned here
|
||||
* one field at a time, because these are the fields a teamer cannot be provisioned without
|
||||
* (.ddev/plans/application-form-teamer-provisioning.md).
|
||||
* one field at a time, because these are the fields a teamer cannot be provisioned without.
|
||||
*/
|
||||
class ApplicationFormControllerTest extends WebTestCase
|
||||
{
|
||||
@@ -412,7 +411,7 @@ class ApplicationFormControllerTest extends WebTestCase
|
||||
|
||||
/**
|
||||
* A duplicate label is disambiguated by the identifier on the TYPO3 side, so both answers
|
||||
* survive as separate keys (§3.3). The order is MySQL's, not the form's - a json column
|
||||
* survive as separate keys. The order is MySQL's, not the form's - a json column
|
||||
* sorts an object's keys by length - so only the set of labels is pinned here.
|
||||
*/
|
||||
public function testEveryLabelSurvivesIncludingADisambiguatedDuplicate(): void
|
||||
@@ -528,7 +527,7 @@ class ApplicationFormControllerTest extends WebTestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* §5.1 is not implemented yet: a double-clicked submit button stores two rows for now, and
|
||||
* Dedupe is not implemented yet: a double-clicked submit button stores two rows for now, and
|
||||
* this pins that it is a known state rather than an accident.
|
||||
*/
|
||||
public function testTheSamePayloadTwiceIsStoredTwiceForNow(): void
|
||||
|
||||
@@ -13,7 +13,7 @@ use App\Service\Assignment\AssignmentPlanner;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* One test per row of the planner table in .ddev/plans/teamer-auto-assignments-plan.md.
|
||||
* Pins the rules listed on AssignmentPlanner, one test per case.
|
||||
*/
|
||||
class AssignmentPlannerTest extends TestCase
|
||||
{
|
||||
|
||||
@@ -24,10 +24,9 @@ use Psr\Log\NullLogger;
|
||||
|
||||
/**
|
||||
* The three provisioning writes are not a transaction and only the first and the third can be
|
||||
* rehearsed against BusPro's test database - the teamer selection is changeable in production only
|
||||
* (.ddev/plans/buspro-crm-selection-write.md). So the orchestration is pinned here against a mocked
|
||||
* client: what runs, in which order, what is persisted before the next step, and what a retry does
|
||||
* after a failure halfway.
|
||||
* rehearsed against BusPro's test database - the teamer selection is changeable in production only.
|
||||
* So the orchestration is pinned here against a mocked client: what runs, in which order, what is
|
||||
* persisted before the next step, and what a retry does after a failure halfway.
|
||||
*/
|
||||
#[AllowMockObjectsWithoutExpectations]
|
||||
class TeamerProvisionerTest extends TestCase
|
||||
|
||||
Reference in new issue
Block a user