Add signalement (flag) tags for problem lines on /lignes
New field_flag: free-tagging taxonomy reference (multi-value, auto-create) on ligne_comptable, for marking a line with an unstructured problem description (e.g. "client impayé") that can't be detected automatically the way the répartition écart already is. - New "Signalement" column, inline-editable like Client/Facture/Libellé (comma-separated tags, datalist autocomplete, server-side auto-create of unknown tags -- same pattern LedgerActionsController already used for Client, now shared via findOrCreateTerm()). - New "Signalées uniquement" filter, mirroring "Écarts uniquement". - Flagged rows get a distinct amber left-edge accent (box-shadow, not border) so a row that's both in écart and signalée shows both indicators without one overwriting the other. - Native node add/edit form gets the field for free via core's entity_reference_autocomplete_tags widget. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,7 @@ class LedgerActionsController extends ControllerBase {
|
||||
'client' => 'field_client',
|
||||
'facture' => 'field_numero_facture',
|
||||
'libelle' => 'field_notes',
|
||||
'flag' => 'field_flag',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -119,8 +120,8 @@ class LedgerActionsController extends ControllerBase {
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /lignes/{node}/champ -- change client/facture/libellé inline,
|
||||
* for clicking directly on those cells in the table. Body:
|
||||
* POST /lignes/{node}/champ -- change client/facture/libellé/signalement
|
||||
* inline, for clicking directly on those cells in the table. Body:
|
||||
* {"field": "client", "value": "EPAU / POPSU"}. An empty value clears
|
||||
* the field (e.g. a structural charge with no client).
|
||||
*/
|
||||
@@ -147,35 +148,26 @@ class LedgerActionsController extends ControllerBase {
|
||||
}
|
||||
|
||||
if ($field === 'client') {
|
||||
if ($value === '') {
|
||||
$node->set('field_client', NULL);
|
||||
}
|
||||
else {
|
||||
$terms = $this->entityTypeManager()->getStorage('taxonomy_term')
|
||||
->loadByProperties(['vid' => 'client', 'name' => $value]);
|
||||
if ($terms) {
|
||||
$node->set('field_client', reset($terms)->id());
|
||||
}
|
||||
else {
|
||||
// No existing term matches -- create one rather than reject,
|
||||
// same "autocreate" behavior as a standard Drupal entity
|
||||
// reference autocomplete widget. The front-end's datalist only
|
||||
// *suggests* known names, it doesn't restrict input to them.
|
||||
$term = Term::create(['vid' => 'client', 'name' => $value]);
|
||||
$term->save();
|
||||
$node->set('field_client', $term->id());
|
||||
}
|
||||
}
|
||||
$node->set('field_client', $value === '' ? NULL : $this->findOrCreateTerm('client', $value)->id());
|
||||
}
|
||||
elseif ($field === 'flag') {
|
||||
// Comma-separated like a native "tags" widget -- one or more
|
||||
// free-form tags, each matched against an existing term or
|
||||
// auto-created (same reasoning as client above). Order/dedup
|
||||
// doesn't matter here, this is a display list, not a répartition.
|
||||
$names = array_unique(array_filter(array_map('trim', explode(',', $value)), fn ($n) => $n !== ''));
|
||||
$tids = array_map(fn ($name) => $this->findOrCreateTerm('flag', $name)->id(), $names);
|
||||
$node->set('field_flag', $tids);
|
||||
}
|
||||
else {
|
||||
$node->set($fieldName, $value !== '' ? $value : NULL);
|
||||
}
|
||||
|
||||
// Same reasoning as updateType() above: only client/facture/libellé
|
||||
// changes here, montant_ht and field_repartition are untouched, so
|
||||
// skipping the répartition check for this save can never introduce a
|
||||
// mismatch -- it can only leave a pre-existing historical one exactly
|
||||
// as it was.
|
||||
// Same reasoning as updateType() above: only client/facture/libellé/
|
||||
// signalement changes here, montant_ht and field_repartition are
|
||||
// untouched, so skipping the répartition check for this save can
|
||||
// never introduce a mismatch -- it can only leave a pre-existing
|
||||
// historical one exactly as it was.
|
||||
\Drupal::state()->set('figli_compta_ledger.skip_validation', TRUE);
|
||||
try {
|
||||
$node->save();
|
||||
@@ -187,9 +179,16 @@ class LedgerActionsController extends ControllerBase {
|
||||
\Drupal::state()->delete('figli_compta_ledger.skip_validation');
|
||||
}
|
||||
|
||||
$newValue = $field === 'client'
|
||||
? ($node->get('field_client')->entity ? $node->get('field_client')->entity->label() : NULL)
|
||||
: $node->get($fieldName)->value;
|
||||
if ($field === 'client') {
|
||||
$newValue = $node->get('field_client')->entity ? $node->get('field_client')->entity->label() : NULL;
|
||||
}
|
||||
elseif ($field === 'flag') {
|
||||
$newValue = array_map(fn ($item) => $item->entity ? $item->entity->label() : NULL, iterator_to_array($node->get('field_flag')));
|
||||
$newValue = array_values(array_filter($newValue));
|
||||
}
|
||||
else {
|
||||
$newValue = $node->get($fieldName)->value;
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'success' => TRUE,
|
||||
@@ -199,6 +198,24 @@ class LedgerActionsController extends ControllerBase {
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds an existing term by name in $vid, or creates one -- shared by
|
||||
* the client and flag (signalement) inline-edit cases above. Same
|
||||
* "autocreate" behavior as a standard Drupal entity reference
|
||||
* autocomplete/tags widget: the front-end's datalist only *suggests*
|
||||
* known names, it doesn't restrict input to them.
|
||||
*/
|
||||
private function findOrCreateTerm(string $vid, string $name): Term {
|
||||
$terms = $this->entityTypeManager()->getStorage('taxonomy_term')
|
||||
->loadByProperties(['vid' => $vid, 'name' => $name]);
|
||||
if ($terms) {
|
||||
return reset($terms);
|
||||
}
|
||||
$term = Term::create(['vid' => $vid, 'name' => $name]);
|
||||
$term->save();
|
||||
return $term;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimistic-locking guard shared by both endpoints above: the
|
||||
* frontend sends the `changed` timestamp of the row it last saw (see
|
||||
|
||||
Reference in New Issue
Block a user