refactor: use english naming for the n8n assignments api

This commit is contained in:
fromme committed 2026-11-07 09:33:54 +01:00
1 parent 0bc77b7463
commit 9583612fdc
10 files changed
+84 -81

No files matched your search

@@ -5,8 +5,8 @@ namespace App\Controller\Api\Assignment;
use App\Entity\Assignment;
use App\Entity\Disposition;
use App\Entity\Upload;
use App\Enum\PlacementStatus;
use App\Model\PlacementExportQueryDto;
use App\Enum\PlaceStatus;
use App\Model\AssignmentExportQueryDto;
use App\Repository\AssignmentRepository;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -17,13 +17,14 @@ use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
/**
* The placements feed n8n reads: one row per place of the houses' guide, course and service-team
* The assignments feed n8n reads: one row per place of the houses' guide, course and service-team
* assignments overlapping a date range. A row is a place, not a person - re-staffing a place
* changes the person on the same platz_id, and an open place has person_id null. A called-off
* assignment is left out entirely, as is a place the office struck.
*
* The path, the query parameters and the field names are the consumer's (German); errors are
* English, like every response of the api firewall.
* The field names are the consumer's (German), the n8n workflow is built on them; the path and the
* query parameters follow the app's English URL vocabulary, and errors are English, like every
* response of the api firewall.
*/
class ListController extends AbstractController
{
@@ -39,19 +40,19 @@ class ListController extends AbstractController
) {
}
#[Route('/api/einsaetze', name: 'app_api_assignment_list', methods: ['GET'])]
#[IsGranted('ROLE_API_PLACEMENTS')]
#[Route('/api/assignments', name: 'app_api_assignment_list', methods: ['GET'])]
#[IsGranted('ROLE_API_ASSIGNMENTS')]
public function index(
#[MapQueryString(validationFailedStatusCode: Response::HTTP_BAD_REQUEST)]
// Deliberately without a default: the resolver would hand that over unvalidated for a
// request without any query string, instead of the 400 naming von and bis.
PlacementExportQueryDto $query,
// request without any query string, instead of the 400 naming from and to.
AssignmentExportQueryDto $query,
): JsonResponse {
$rows = [];
$assignments = $this->assignmentRepository->findForPlacementExport(
$query->getFrom(),
$query->getTo(),
$assignments = $this->assignmentRepository->findForExport(
$query->getFromDate(),
$query->getToDate(),
$this->houseCodes,
$this->jobProfileIds,
);
@@ -63,9 +64,9 @@ class ListController extends AbstractController
}
// the range and the count, never the rows - they name people
$this->logger->info('Placements exported', [
'from' => $query->von,
'to' => $query->bis,
$this->logger->info('Assignments exported', [
'from' => $query->from,
'to' => $query->to,
'assignments' => count($assignments),
'places' => count($rows),
]);
@@ -94,7 +95,7 @@ class ListController extends AbstractController
'bis' => $destination->getDateTo()->format('Y-m-d'),
'jobprofil_id' => $jobProfile->getId(),
'jobprofil' => $jobProfile->getName(),
'status' => PlacementStatus::fromDisposition($disposition)->value,
'status' => PlaceStatus::fromDisposition($disposition)->value,
'unter_26' => $this->isUnder26($disposition, $assignment),
'vertrag_liegt_vor' => $this->hasCheckedContract($disposition),
// Older codes carry a slash ("DPWBSP/030229"), current ones do not.
@@ -5,9 +5,9 @@ namespace App\Enum;
use App\Entity\Disposition;
/**
* The status of a place in the n8n placements feed. The values are the feed's contract.
* The status of a place in the n8n assignments feed. The values are the feed's contract.
*/
enum PlacementStatus: string
enum PlaceStatus: string
{
case OPEN = 'offen';
case REQUESTED = 'angefragt';
+35
View File
@@ -0,0 +1,35 @@
<?php
namespace App\Model;
use Symfony\Component\Validator\Constraints as Assert;
/**
* The date range of GET /api/assignments, mapped from the query string. The parameter names follow
* the app's English URL vocabulary, like the path.
*/
class AssignmentExportQueryDto
{
public function __construct(
#[Assert\NotBlank(message: 'from must not be empty.')]
#[Assert\Date(message: 'from must be a date as YYYY-MM-DD.')]
public readonly ?string $from = null,
#[Assert\NotBlank(message: 'to must not be empty.')]
#[Assert\Date(message: 'to must be a date as YYYY-MM-DD.')]
#[Assert\GreaterThanOrEqual(propertyPath: 'from', message: 'to must not be before from.')]
public readonly ?string $to = null,
) {
}
// Not getFrom()/getTo(): the property accessor prefers a getter over the public property, so
// the GreaterThanOrEqual above would compare the string "to" with a DateTimeImmutable.
public function getFromDate(): \DateTimeImmutable
{
return new \DateTimeImmutable($this->from);
}
public function getToDate(): \DateTimeImmutable
{
return new \DateTimeImmutable($this->to);
}
}
-33
View File
@@ -1,33 +0,0 @@
<?php
namespace App\Model;
use Symfony\Component\Validator\Constraints as Assert;
/**
* The date range of GET /api/einsaetze, mapped from the query string. The parameter names are
* the consumer's (n8n).
*/
class PlacementExportQueryDto
{
public function __construct(
#[Assert\NotBlank(message: 'von must not be empty.')]
#[Assert\Date(message: 'von must be a date as YYYY-MM-DD.')]
public readonly ?string $von = null,
#[Assert\NotBlank(message: 'bis must not be empty.')]
#[Assert\Date(message: 'bis must be a date as YYYY-MM-DD.')]
#[Assert\GreaterThanOrEqual(propertyPath: 'von', message: 'bis must not be before von.')]
public readonly ?string $bis = null,
) {
}
public function getFrom(): \DateTimeImmutable
{
return new \DateTimeImmutable($this->von);
}
public function getTo(): \DateTimeImmutable
{
return new \DateTimeImmutable($this->bis);
}
}
+2 -2
View File
@@ -433,7 +433,7 @@ class AssignmentRepository extends ServiceEntityRepository
}
/**
* The assignments the n8n placements feed reports: published, in one of the houses, for one of
* The assignments the n8n assignments feed reports: published, in one of the houses, for one of
* the job profiles, and overlapping the range. Placements, their teamers and documents come
* fetch-joined - every place row needs them. All placements are loaded, called-off ones included:
* a fetch join on a filtered collection would leave the assignment with a partial one, and
@@ -444,7 +444,7 @@ class AssignmentRepository extends ServiceEntityRepository
*
* @return list<Assignment>
*/
public function findForPlacementExport(\DateTimeImmutable $from, \DateTimeImmutable $to, array $houseCodes, array $jobProfileIds): array
public function findForExport(\DateTimeImmutable $from, \DateTimeImmutable $to, array $houseCodes, array $jobProfileIds): array
{
if ([] === $houseCodes || [] === $jobProfileIds) {
return [];
@@ -14,7 +14,7 @@ use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
* to this application's UI.
*
* One key per caller, each resolving to its own user and roles: the websites posting application
* forms must not read placements, and n8n reading placements must not post forms.
* forms must not read assignments, and n8n reading assignments must not post forms.
*/
class ApiKeyTokenHandler implements AccessTokenHandlerInterface
{