fix(books): correction du tri naturel des titres avec sous-titres variables

Remplace REGEXP_REPLACE(title, '[0-9]+', '', 'g') par
REGEXP_REPLACE(title, '[0-9].*$', '') pour n'extraire que le préfixe
avant le premier chiffre.

L'ancienne regex supprimait TOUS les chiffres du titre entier, y compris
dans les sous-titres. Quand chaque volume a un sous-titre différent
(ex: "Deadpool marvel deluxe 3 - Je suis ton homme"), la partie texte
restante variait → l'ordre alphabétique des sous-titres prenait le dessus.

Avec le nouveau préfixe "deadpool marvel deluxe " identique pour tous,
le numéro de volume (2ème clé) dicte correctement l'ordre.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-16 12:16:00 +01:00
parent 9fbdf793d0
commit a085924f8a

View File

@@ -141,7 +141,7 @@ pub async fn list_books(
let order_clause = if query.sort.as_deref() == Some("latest") { let order_clause = if query.sort.as_deref() == Some("latest") {
"b.updated_at DESC".to_string() "b.updated_at DESC".to_string()
} else { } else {
"b.volume NULLS LAST, REGEXP_REPLACE(LOWER(b.title), '[0-9]+', '', 'g'), COALESCE((REGEXP_MATCH(LOWER(b.title), '\\d+'))[1]::int, 0), b.title ASC".to_string() "b.volume NULLS LAST, REGEXP_REPLACE(LOWER(b.title), '[0-9].*$', ''), COALESCE((REGEXP_MATCH(LOWER(b.title), '\\d+'))[1]::int, 0), b.title ASC".to_string()
}; };
// DATA: mêmes params filtre, puis $N+1=limit $N+2=offset // DATA: mêmes params filtre, puis $N+1=limit $N+2=offset
@@ -401,7 +401,7 @@ pub async fn list_series(
PARTITION BY COALESCE(NULLIF(series, ''), 'unclassified') PARTITION BY COALESCE(NULLIF(series, ''), 'unclassified')
ORDER BY ORDER BY
volume NULLS LAST, volume NULLS LAST,
REGEXP_REPLACE(LOWER(title), '[0-9]+', '', 'g'), REGEXP_REPLACE(LOWER(title), '[0-9].*$', ''),
COALESCE((REGEXP_MATCH(LOWER(title), '\d+'))[1]::int, 0), COALESCE((REGEXP_MATCH(LOWER(title), '\d+'))[1]::int, 0),
title ASC title ASC
) as rn ) as rn
@@ -428,7 +428,7 @@ pub async fn list_series(
{q_cond} {q_cond}
{count_rs_cond} {count_rs_cond}
ORDER BY ORDER BY
REGEXP_REPLACE(LOWER(sc.name), '[0-9]+', '', 'g'), REGEXP_REPLACE(LOWER(sc.name), '[0-9].*$', ''),
COALESCE( COALESCE(
(REGEXP_MATCH(LOWER(sc.name), '\d+'))[1]::int, (REGEXP_MATCH(LOWER(sc.name), '\d+'))[1]::int,
0 0
@@ -570,7 +570,7 @@ pub async fn list_all_series(
let series_order_clause = if query.sort.as_deref() == Some("latest") { let series_order_clause = if query.sort.as_deref() == Some("latest") {
"sc.latest_updated_at DESC".to_string() "sc.latest_updated_at DESC".to_string()
} else { } else {
"REGEXP_REPLACE(LOWER(sc.name), '[0-9]+', '', 'g'), COALESCE((REGEXP_MATCH(LOWER(sc.name), '\\d+'))[1]::int, 0), sc.name ASC".to_string() "REGEXP_REPLACE(LOWER(sc.name), '[0-9].*$', ''), COALESCE((REGEXP_MATCH(LOWER(sc.name), '\\d+'))[1]::int, 0), sc.name ASC".to_string()
}; };
let limit_p = p + 1; let limit_p = p + 1;
@@ -588,7 +588,7 @@ pub async fn list_all_series(
PARTITION BY COALESCE(NULLIF(series, ''), 'unclassified') PARTITION BY COALESCE(NULLIF(series, ''), 'unclassified')
ORDER BY ORDER BY
volume NULLS LAST, volume NULLS LAST,
REGEXP_REPLACE(LOWER(title), '[0-9]+', '', 'g'), REGEXP_REPLACE(LOWER(title), '[0-9].*$', ''),
COALESCE((REGEXP_MATCH(LOWER(title), '\d+'))[1]::int, 0), COALESCE((REGEXP_MATCH(LOWER(title), '\d+'))[1]::int, 0),
title ASC title ASC
) as rn ) as rn
@@ -717,7 +717,7 @@ pub async fn ongoing_series(
PARTITION BY COALESCE(NULLIF(series, ''), 'unclassified') PARTITION BY COALESCE(NULLIF(series, ''), 'unclassified')
ORDER BY ORDER BY
volume NULLS LAST, volume NULLS LAST,
REGEXP_REPLACE(LOWER(title), '[0-9]+', '', 'g'), REGEXP_REPLACE(LOWER(title), '[0-9].*$', ''),
COALESCE((REGEXP_MATCH(LOWER(title), '\d+'))[1]::int, 0), COALESCE((REGEXP_MATCH(LOWER(title), '\d+'))[1]::int, 0),
title ASC title ASC
) AS rn ) AS rn