Link sorties to the entrée client they pay out against
New field_entree_liee (entity reference, node -> node) on
ligne_comptable, restricted to entrée-type nodes via a custom
EntreeClientSelection plugin -- narrows further to the same client as
the sortie being linked when one is already known, using the
referencing-entity context Drupal's selection handler API passes
through (getSelectionHandler($field, $entity)).
Quick-link UI (per the associates' explicit ask: no need to open the
full ligne_comptable form just for this):
- A chain-link icon next to the edit pencil on versement/achat/
hébergement rows (the only types that pay out against a client
invoice) opens LinkEntreeForm, a one-field AJAX modal, reusing the
same modal/close-on-save plumbing as the edit form. Filled/colored
when already linked, with the linked entrée's label on hover.
Also present (states-hidden unless one of those three types is
selected) on the full node form for whoever's already there anyway.
- Entrée rows get a reconciliation badge once at least one sortie
links back to them, clickable to drill the table down to just that
entrée and its linked sorties.
Conformity check assumes multi-compte répartition on both sides (an
entrée's répartition and each linked sortie's répartition can each
split across several comptes -- confirmed this is the real shape of
"hébergement" sorties, e.g. OVH/HETZNER renewals split across all 8
comptes, even though versement/achat lines happen to always be
single-compte in the current data). Per compte, compares the entrée's
répartition share (positive, owed) against the summed répartition of
every linked sortie (negative, paid) -- a residual near zero means
settled, positive means still owed ("reste à verser"), negative means
overpaid ("sur-versé", flagged for a closer look).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\figli_compta_ledger\Form;
|
||||
|
||||
use Drupal\Core\Ajax\AjaxResponse;
|
||||
use Drupal\Core\Ajax\CloseModalDialogCommand;
|
||||
use Drupal\Core\Ajax\ReplaceCommand;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\node\NodeInterface;
|
||||
|
||||
/**
|
||||
* Quick-link form: sets field_entree_liee on a single sortie line without
|
||||
* opening the full ligne_comptable edit form -- the associates only ever
|
||||
* need to touch this one field to link a versement/achat/hébergement to
|
||||
* the entrée client it pays out against.
|
||||
*/
|
||||
class LinkEntreeForm extends FormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'figli_compta_ledger_link_entree_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, ?NodeInterface $node = NULL) {
|
||||
$form_state->set('node', $node);
|
||||
|
||||
$form['field_entree_liee'] = [
|
||||
'#type' => 'entity_autocomplete',
|
||||
'#title' => $this->t('Entrée client liée'),
|
||||
'#target_type' => 'node',
|
||||
'#selection_handler' => 'figli_compta_ledger:entree_client',
|
||||
'#selection_settings' => [
|
||||
'target_bundles' => ['ligne_comptable' => 'ligne_comptable'],
|
||||
// Narrows the autocomplete to the same client as this sortie --
|
||||
// read by EntreeClientSelection::buildEntityQuery().
|
||||
'entity' => $node,
|
||||
],
|
||||
'#default_value' => $node->get('field_entree_liee')->entity,
|
||||
'#description' => $this->t('Laisser vide pour retirer le lien.'),
|
||||
];
|
||||
|
||||
$form['actions'] = ['#type' => 'actions'];
|
||||
$form['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Enregistrer'),
|
||||
'#ajax' => [
|
||||
'callback' => '::ajaxSubmit',
|
||||
],
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
/** @var \Drupal\node\NodeInterface $node */
|
||||
$node = $form_state->get('node');
|
||||
$node->set('field_entree_liee', $form_state->getValue('field_entree_liee') ?: NULL);
|
||||
$node->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* #ajax callback: close the modal on success (the /lignes table picks up
|
||||
* "dialog:afterclose" to refresh), or re-render the form in place.
|
||||
*/
|
||||
public function ajaxSubmit(array &$form, FormStateInterface $form_state) {
|
||||
$response = new AjaxResponse();
|
||||
if ($form_state->getErrors()) {
|
||||
unset($form['#prefix'], $form['#suffix']);
|
||||
$response->addCommand(new ReplaceCommand('#' . $form['#id'], $form));
|
||||
return $response;
|
||||
}
|
||||
$response->addCommand(new CloseModalDialogCommand());
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\figli_compta_ledger\Plugin\EntityReferenceSelection;
|
||||
|
||||
use Drupal\Core\Entity\Attribute\EntityReferenceSelection;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\node\Plugin\EntityReferenceSelection\NodeSelection;
|
||||
|
||||
/**
|
||||
* Restricts field_entree_liee's candidates to "entrée" ligne_comptable
|
||||
* nodes, scoped to the same client as the sortie being linked when one is
|
||||
* already set on it.
|
||||
*/
|
||||
#[EntityReferenceSelection(
|
||||
id: "figli_compta_ledger:entree_client",
|
||||
label: new TranslatableMarkup("Entrées client (SAS Figures Libres)"),
|
||||
entity_types: ["node"],
|
||||
group: "figli_compta_ledger",
|
||||
weight: 0,
|
||||
)]
|
||||
class EntreeClientSelection extends NodeSelection {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
|
||||
$query = parent::buildEntityQuery($match, $match_operator);
|
||||
$query->condition('field_type_ligne', 'entree');
|
||||
|
||||
$entity = $this->configuration['entity'] ?? NULL;
|
||||
if ($entity && $entity->hasField('field_client') && !$entity->get('field_client')->isEmpty()) {
|
||||
$query->condition('field_client', $entity->get('field_client')->target_id);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user