Inline-edit the type badge directly in the table
Clicking a row's type badge swaps it for a native <select> in place; picking a new value POSTs to a new endpoint (LedgerActionsController::updateType) instead of opening the full edit modal for this one field. The endpoint goes through the normal node save() lifecycle, so figli_compta_ledger_node_presave() still forces a proper revision and still enforces the répartition invariant -- nothing here bypasses that. It also clears a stale field_entree_liee when the new type is no longer linkable (versement/achat/hébergement), mirroring the full form's #states visibility rule. CSRF-protected via core's own /session/token, scoped to CsrfRequestHeaderAccessCheck::TOKEN_KEY to match what that endpoint actually generates. Verified end-to-end via the real click flow: correct revision (user + timestamp), correct optimistic UI update, correct field_entree_liee clearing, and 400/403 on invalid type / missing CSRF respectively.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\figli_compta_ledger\Controller;
|
||||
|
||||
use Drupal\Core\Access\CsrfRequestHeaderAccessCheck;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Entity\EntityStorageException;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Small write endpoints backing inline (no-modal) edits from the /lignes
|
||||
* table. Each goes through the normal node save() lifecycle -- same as the
|
||||
* full edit form -- so figli_compta_ledger_node_presave() still forces a
|
||||
* proper revision and still enforces the répartition invariant; nothing
|
||||
* here bypasses that.
|
||||
*/
|
||||
class LedgerActionsController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* Every value field_type_ligne actually allows (see the field's
|
||||
* allowed_values in config) -- validated against here rather than
|
||||
* trusting the client.
|
||||
*/
|
||||
const ALLOWED_TYPES = ['entree', 'charge', 'versement', 'achat', 'hebergement', 'autre', 'ouverture'];
|
||||
|
||||
/**
|
||||
* Types field_entree_liee is meaningful for -- mirrors the #states
|
||||
* visibility rule in figli_compta_ledger_form_alter().
|
||||
*/
|
||||
const LINKABLE_TYPES = ['versement', 'achat', 'hebergement'];
|
||||
|
||||
/**
|
||||
* POST /lignes/{node}/type -- change field_type_ligne without opening
|
||||
* the full edit form, for clicking the type badge directly in the
|
||||
* table. Body: {"type": "charge"}.
|
||||
*/
|
||||
public function updateType(Request $request, NodeInterface $node) {
|
||||
if ($node->bundle() !== 'ligne_comptable') {
|
||||
return new JsonResponse(['error' => 'Type de contenu invalide.'], 404);
|
||||
}
|
||||
|
||||
// Scoped to CsrfRequestHeaderAccessCheck::TOKEN_KEY -- the same value
|
||||
// core's own /session/token controller generates against, which is
|
||||
// what the frontend fetches this token from.
|
||||
$csrfToken = $request->headers->get('X-CSRF-Token', '');
|
||||
if (!\Drupal::csrfToken()->validate($csrfToken, CsrfRequestHeaderAccessCheck::TOKEN_KEY)) {
|
||||
return new JsonResponse(['error' => 'Jeton de sécurité invalide, rechargez la page.'], 403);
|
||||
}
|
||||
|
||||
$data = json_decode($request->getContent(), TRUE);
|
||||
$type = is_array($data) ? ($data['type'] ?? NULL) : NULL;
|
||||
if (!in_array($type, self::ALLOWED_TYPES, TRUE)) {
|
||||
return new JsonResponse(['error' => 'Type de ligne invalide.'], 400);
|
||||
}
|
||||
|
||||
$node->set('field_type_ligne', $type);
|
||||
|
||||
// A type that's no longer linkable shouldn't keep a stale
|
||||
// field_entree_liee reference around (mirrors the form's #states:
|
||||
// charge/autre/ouverture/entree don't expose that field at all).
|
||||
if (!in_array($type, self::LINKABLE_TYPES, TRUE)
|
||||
&& $node->hasField('field_entree_liee')
|
||||
&& !$node->get('field_entree_liee')->isEmpty()) {
|
||||
$node->set('field_entree_liee', NULL);
|
||||
}
|
||||
|
||||
try {
|
||||
$node->save();
|
||||
}
|
||||
catch (EntityStorageException $e) {
|
||||
return new JsonResponse(['error' => $e->getMessage()], 422);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'success' => TRUE,
|
||||
'type' => $type,
|
||||
'entree_liee_cleared' => !in_array($type, self::LINKABLE_TYPES, TRUE),
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user