Sliding-window loading for the grand livre + year-scoped sticky footer
With 5+ years of migrated history, loading every ligne up front took ~40s. The table now only ever holds a date-range window (~18 months around today by default), extended by 6 months when scrolling near either edge and trimmed from the far end past a 30-month cap. The totals footer and "Année" filter can't be answered from a partial window, so they're backed by two new small endpoints (LedgerStatsController) instead: per-compte totals for whichever year is currently scrolled into view, and the distinct list of years with data.
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\figli_compta_ledger\Controller;
|
||||
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Small aggregate endpoints backing the /lignes sliding window: the
|
||||
* row-level JSON:API fetch only ever covers a date range (see home.js), so
|
||||
* neither the totals footer nor the "Année" dropdown can be computed from
|
||||
* whatever's currently loaded -- they need their own always-accurate
|
||||
* queries, decoupled from the row window.
|
||||
*/
|
||||
class LedgerStatsController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* GET /lignes/api/totaux?annee=2023 -- per-compte répartition sums (plus
|
||||
* montant HT/TTC/écart totals) for every ligne_comptable dated that
|
||||
* year. Ouverture lines are excluded: they're a carried-over balance,
|
||||
* not activity within the year.
|
||||
*/
|
||||
public function totauxAnnee(Request $request) {
|
||||
$annee = $request->query->get('annee');
|
||||
if (!$annee || !preg_match('/^\d{4}$/', $annee)) {
|
||||
return new JsonResponse(['error' => 'Paramètre "annee" invalide.'], 400);
|
||||
}
|
||||
|
||||
$storage = $this->entityTypeManager()->getStorage('node');
|
||||
$nids = $storage->getQuery()
|
||||
->accessCheck(TRUE)
|
||||
->condition('type', 'ligne_comptable')
|
||||
->condition('field_date_ligne', $annee . '-01-01', '>=')
|
||||
->condition('field_date_ligne', ((int) $annee + 1) . '-01-01', '<')
|
||||
->condition('field_type_ligne', 'ouverture', '<>')
|
||||
->execute();
|
||||
|
||||
$par_compte = [];
|
||||
$montant_ht = 0.0;
|
||||
$montant_ttc = 0.0;
|
||||
$ecart = 0.0;
|
||||
foreach ($storage->loadMultiple($nids) as $node) {
|
||||
$ht = $node->hasField('field_montant_ht') && !$node->get('field_montant_ht')->isEmpty()
|
||||
? (float) $node->get('field_montant_ht')->value : 0.0;
|
||||
$ttc = $node->hasField('field_montant_ttc') && !$node->get('field_montant_ttc')->isEmpty()
|
||||
? (float) $node->get('field_montant_ttc')->value : 0.0;
|
||||
$montant_ht += $ht;
|
||||
$montant_ttc += $ttc;
|
||||
|
||||
$somme = 0.0;
|
||||
foreach ($node->get('field_repartition')->referencedEntities() as $paragraph) {
|
||||
if (!$paragraph->hasField('field_montant') || $paragraph->get('field_montant')->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
$montant = (float) $paragraph->get('field_montant')->value;
|
||||
$compte = $paragraph->get('field_compte')->entity ? $paragraph->get('field_compte')->entity->label() : NULL;
|
||||
if ($compte) {
|
||||
$par_compte[$compte] = ($par_compte[$compte] ?? 0) + $montant;
|
||||
}
|
||||
$somme += $montant;
|
||||
}
|
||||
$ecart += round($ht - $somme, 2);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'annee' => $annee,
|
||||
'montant_ht' => round($montant_ht, 2),
|
||||
'montant_ttc' => round($montant_ttc, 2),
|
||||
'ecart' => round($ecart, 2),
|
||||
'par_compte' => array_map(fn ($v) => round($v, 2), $par_compte),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /lignes/api/annees -- distinct years, most recent first, with at
|
||||
* least 5 lines. The threshold exists specifically to keep the handful
|
||||
* of mistyped historical dates (preserved as-is -- e.g. a "0213" typo
|
||||
* for "2023") from polluting the year filter with bogus one-line
|
||||
* "years". A plain SQL aggregate, not Entity API: this only needs the
|
||||
* date column, not full node loads.
|
||||
*/
|
||||
public function annees() {
|
||||
$connection = \Drupal::database();
|
||||
$query = $connection->select('node__field_date_ligne', 'd');
|
||||
$query->addExpression('SUBSTRING(d.field_date_ligne_value, 1, 4)', 'annee');
|
||||
$query->addExpression('COUNT(*)', 'total');
|
||||
$query->condition('d.bundle', 'ligne_comptable');
|
||||
$query->groupBy('annee');
|
||||
$query->having('COUNT(*) >= 5');
|
||||
$results = $query->execute()->fetchCol();
|
||||
|
||||
$annees = array_values($results);
|
||||
rsort($annees);
|
||||
|
||||
return new JsonResponse(['annees' => $annees]);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user