feat(rust) : serveur réécrit en Rust, Node retiré ; Calendrier en tête du menu (#571)

- Serveur Rust (server-rs/, axum + sqlx) : les 148 adresses de l'API,
  l'interface, les migrations de démarrage et les tâches de fond ; même
  comportement que Node (banc : 2027 cas, 0 écart hors écarts déclarés
  USERS-02, RAPPRO-21, R4, R5).
- Node retiré : server/, dépendances serveur, image Docker sans Node
  (51 Mo), docker-compose à un seul service applicatif.
- R4 : écritures d'un autre site refusées (Sec-Fetch-Site / Origin).
- R5 : « aujourd'hui » et sauvegarde automatique à l'heure de Paris.
- R6 : mainteneur dans .github/CODEOWNERS.
- Menu latéral : Accueil puis Calendrier en tête.
- Plan et état : docs/PLAN-MIGRATION-RUST.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ke2qyS8rgacP3ap5iezDbM
This commit is contained in:
LogiFlowandClaude Opus 5.5 authored and GitHub committed 2026-10-15 20:31:14 +02:00
1 parent 6760f43e87
commit 600627c632
146 files changed
+31521 -20331

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
Données complémentaires du banc, une par lot : chaque fichier `*.ts` exporte
`seed(q)` (voir `seed.ts`), appelée après les données de base, par ordre
alphabétique de nom de fichier. Identifiants à partir de 100 pour ne pas
heurter ceux de `seed.ts` ni ceux d'un autre fichier : choisir une centaine
libre (lot 2 : 200…, lot 3 : 300…, lot 4 : 400…, lot 5 : 500…).
Les fichiers de `sauvegardes/` (sauvegardes déclarées dans `database_backups`)
sont copiés par `compare.ts` dans le dossier de sauvegardes de chaque serveur.
+84
View File
@@ -0,0 +1,84 @@
// Données du lot 2 (Informations et planning), identifiants à partir de 200 :
// - informations au-delà de la limite de 5, par magasin et globales, avec des
// auteurs de chaque forme (prénom et nom, nom d'affichage seul, prénom seul,
// compte disparu) ;
// - publicités dont les N° testent le tri numérique (« 12 », « 3 », « 3-bis »)
// et des participations du magasin d'Épinal ;
// - une commande livrée en retard par magasin hors Nancy, pour des délais moyens
// non nuls dans les statistiques ;
// - la météo en cache pour aujourd'hui et pour la date de l'an dernier calculée
// par le serveur Node : /api/weather/current ne sollicite jamais Visual
// Crossing pendant le banc (fenêtre de 9 jours, au cas où le banc tourne
// quelques jours après setup.sh).
import { previousYearDate } from "../legacy";
type Query = (text: string, values?: unknown[]) => Promise<unknown>;
function isoDay(offset: number): string {
const date = new Date();
date.setUTCDate(date.getUTCDate() + offset);
return date.toISOString().slice(0, 10);
}
export async function seed(q: Query) {
// Auteurs d'informations sans magasin (connexion impossible : empreinte factice)
await q(`INSERT INTO users (id, username, email, name, first_name, last_name, password, role, password_changed, created_at, updated_at) VALUES
('bench_lot2_redaction', 'redaction', 'redaction@banc.test', 'Équipe Rédaction Nancy', NULL, NULL, 'x', 'employee', true, CURRENT_DATE - 90 + time '08:00', CURRENT_DATE - 90 + time '08:00'),
('bench_lot2_prenom', 'prenom-seul', NULL, NULL, 'Paulette', NULL, 'x', 'employee', true, CURRENT_DATE - 90 + time '08:00', CURRENT_DATE - 90 + time '08:00')`);
await q(`INSERT INTO dashboard_messages (id, title, content, type, store_id, created_by, created_at) VALUES
(200, 'Fermeture exceptionnelle', 'Fermeture à 18 h samedi', 'error', 1, 'manager', CURRENT_DATE - 2 + time '09:00'),
(201, 'Nouvelle procédure', 'Procédure de réception mise à jour', 'success', NULL, 'admin', CURRENT_DATE - 3 + time '10:00'),
(202, 'Réunion Metz', 'Réunion d''équipe jeudi', 'info', 2, 'directeur', CURRENT_DATE - 1 + time '14:00'),
(203, 'Message de la rédaction', 'Nouvelle charte affichage', 'warning', 3, 'redaction', CURRENT_DATE - 5 + time '11:00'),
(204, 'Prénom seul', 'Auteur sans nom de famille', 'info', NULL, 'prenom-seul', CURRENT_DATE - 4 + time '16:30'),
(205, 'Auteur supprimé', 'Compte disparu depuis', 'info', 1, 'ancien-compte', CURRENT_DATE - 6 + time '08:15')`);
const year = new Date().getUTCFullYear();
await q(`INSERT INTO publicities (id, pub_number, designation, start_date, end_date, year, created_by, created_at, updated_at) VALUES
(200, '12', 'Promo hiver', CURRENT_DATE + 40, CURRENT_DATE + 50, $1, 'admin_local', CURRENT_DATE - 30 + time '09:00', CURRENT_DATE - 30 + time '09:00'),
(201, '3', 'Semaine du blanc', CURRENT_DATE - 3, CURRENT_DATE + 4, $1, 'admin_local', CURRENT_DATE - 40 + time '09:00', CURRENT_DATE - 35 + time '09:00'),
(202, '3-bis', 'Semaine du blanc (suite)', CURRENT_DATE + 5, CURRENT_DATE + 9, $1, 'admin_local', CURRENT_DATE - 40 + time '09:30', CURRENT_DATE - 40 + time '09:30'),
(203, 'PUB-03', 'Catalogue de l''an prochain', CURRENT_DATE + 200, CURRENT_DATE + 214, $2, 'admin_local', CURRENT_DATE - 10 + time '09:00', CURRENT_DATE - 10 + time '09:00')`,
[year, year + 1]);
await q(`INSERT INTO publicity_participations (publicity_id, group_id, created_at) VALUES
(200, 3, CURRENT_DATE - 30 + time '09:00'), (201, 1, CURRENT_DATE - 40 + time '09:00'), (201, 3, CURRENT_DATE - 40 + time '09:00'),
(203, 2, CURRENT_DATE - 10 + time '09:00')`);
await q(`INSERT INTO orders (id, supplier_id, group_id, planned_date, quantity, unit, status, notes, created_by, created_at, updated_at) VALUES
(200, 2, 2, CURRENT_DATE - 12, 7, 'palettes', 'delivered', NULL, 'bench_directeur', CURRENT_DATE - 14 + time '10:00', CURRENT_DATE - 9 + time '15:10'),
(201, 4, 3, CURRENT_DATE - 15, 2, 'colis', 'delivered', NULL, 'admin_local', CURRENT_DATE - 16 + time '10:00', CURRENT_DATE - 13 + time '10:10')`);
await q(`INSERT INTO deliveries (id, order_id, supplier_id, group_id, scheduled_date, delivered_date, quantity, unit, status, notes, bl_number, bl_amount,
invoice_reference, invoice_amount, invoice_amount_ttc, due_date, reconciled, validated_at, control_validated, created_by, created_at, updated_at) VALUES
(200, 200, 2, 2, CURRENT_DATE - 12, CURRENT_DATE - 9 + time '15:00', 7, 'palettes', 'delivered', NULL, 'BL-2200', 640.40,
'FAC-2200', 640.40, 768.48, CURRENT_DATE + 20, false, CURRENT_DATE - 9 + time '15:10', false, 'bench_directeur', CURRENT_DATE - 14 + time '10:00', CURRENT_DATE - 9 + time '15:10'),
(201, 201, 4, 3, CURRENT_DATE - 15, CURRENT_DATE - 13 + time '10:00', 2, 'colis', 'delivered', NULL, 'BL-2201', NULL,
NULL, NULL, NULL, NULL, false, CURRENT_DATE - 13 + time '10:10', false, 'admin_local', CURRENT_DATE - 16 + time '10:00', CURRENT_DATE - 13 + time '10:10')`);
// Météo en cache : même date de l'an dernier que GET /api/weather/current
const conditions = [
["partly-cloudy-day", "Partiellement nuageux"],
["rain", "Pluie légère"],
["clear-day", "Dégagé"],
["fog", "Brouillard"],
["icone-inconnue", "Variable"],
];
const rows: unknown[][] = [];
const previousDates = new Set<string>();
for (let offset = -1; offset <= 7; offset++) {
const day = isoDay(offset);
const [icon, label] = conditions[(offset + 1) % conditions.length];
rows.push([day, "Nancy, France", (14.3 + offset).toFixed(1), (6 + offset / 2).toString(), icon, label, true]);
const previous = previousYearDate(new Date(`${day}T12:00:00Z`));
if (!previousDates.has(previous)) {
previousDates.add(previous);
const [oldIcon, oldLabel] = conditions[(offset + 3) % conditions.length];
rows.push([previous, "Nancy, France", (12 + offset).toString(), (3.5 - offset).toFixed(2), oldIcon, oldLabel, false]);
}
}
let id = 200;
for (const row of rows) {
await q(`INSERT INTO weather_data (id, date, location, temp_max, temp_min, icon, conditions, is_current_year, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, CURRENT_DATE - 1 + time '06:00', CURRENT_DATE - 1 + time '06:00')`, [id++, ...row]);
}
}
+52
View File
@@ -0,0 +1,52 @@
// Lot 3 « Au quotidien » : tâches, DLC, commandes clients et SAV (identifiants 300…).
// Complète seed.ts pour exercer ce que ses lignes ne couvrent pas : une ligne du
// magasin d'Épinal (aucun compte du banc n'y est affecté, sauf l'administrateur),
// les bornes de date des filtres (aujourd'hui, J+15, J+16) et des tâches
// programmées plus tard dans la journée.
type Query = (text: string, values?: unknown[]) => Promise<unknown>;
export async function seed(q: Query) {
await q(`INSERT INTO tasks (id, title, description, start_date, due_date, priority, status, assigned_to, created_by, group_id, completed_at, completed_by, created_at, updated_at) VALUES
(300, 'Inventaire Épinal', 'Magasin sans compte du banc', NULL, CURRENT_DATE + 4, 'medium', 'pending', 'Équipe Épinal', 'admin_local', 3, NULL, NULL, CURRENT_DATE - 2 + time '09:00', CURRENT_DATE - 2 + time '09:00'),
(301, 'Réception livraison', NULL, CURRENT_DATE + time '00:00', CURRENT_DATE, 'high', 'pending', 'Marc', 'bench_manager', 1, NULL, NULL, CURRENT_DATE - 1 + time '07:00', CURRENT_DATE - 1 + time '07:00'),
(302, 'Préparer la vitrine', NULL, CURRENT_DATE + time '10:00', CURRENT_DATE + 1, 'low', 'pending', 'Sophie', 'bench_directeur', 1, NULL, NULL, CURRENT_DATE - 1 + time '06:00', CURRENT_DATE - 1 + time '06:00'),
(303, 'Commande ancienne', NULL, CURRENT_DATE - 10 + time '08:00', CURRENT_DATE - 9, 'medium', 'completed', 'Léa', 'bench_directeur', 2, CURRENT_DATE - 9 + time '17:00', 'bench_directeur', CURRENT_DATE - 12 + time '08:00', CURRENT_DATE - 9 + time '17:00')`);
await q(`INSERT INTO dlc_products (id, name, product_name, gencode, supplier_id, group_id, expiry_date, date_type, quantity, unit, location, alert_threshold,
status, stock_epuise, stock_epuise_by, stock_epuise_at, processed_at, processed_by, processed_until_expiry, notes, created_by,
validated_by, validated_at, created_at, updated_at) VALUES
(300, 'Saucisson', 'Saucisson sec', '3000000000301', 1, 3, CURRENT_DATE + 5, 'dlc', 8, 'unité', 'Magasin', 15,
'en_cours', false, NULL, NULL, NULL, NULL, false, NULL, 'admin_local', NULL, NULL, CURRENT_DATE - 2 + time '08:30', CURRENT_DATE - 2 + time '08:30'),
(301, 'Lait demi-écrémé', 'Lait demi-écrémé 1 L', '3000000000318', 1, 1, CURRENT_DATE + 30, 'dlc', 20, 'unité', 'Réserve', 15,
'en_cours', false, NULL, NULL, NULL, NULL, false, NULL, 'bench_employee', NULL, NULL, CURRENT_DATE - 1 + time '08:30', CURRENT_DATE - 1 + time '08:30'),
(302, 'Compote', 'Compote pomme x4', '3000000000325', 2, 1, CURRENT_DATE, 'ddm', 5, 'unité', 'Magasin', 15,
'en_cours', false, NULL, NULL, NULL, NULL, false, NULL, 'bench_manager', NULL, NULL, CURRENT_DATE - 8 + time '08:30', CURRENT_DATE - 8 + time '08:30'),
(303, 'Jus d''orange', NULL, NULL, 2, 1, CURRENT_DATE + 15, 'dluo', 3, 'unité', 'Magasin', 15,
'en_cours', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'bench_manager', NULL, NULL, CURRENT_DATE - 6 + time '09:30', CURRENT_DATE - 6 + time '09:30'),
(304, 'Fromage blanc', 'Fromage blanc 500 g', '3000000000349', 1, 2, CURRENT_DATE + 16, 'dlc', 7, 'unité', 'Rayon frais', 15,
'en_cours', false, NULL, NULL, NULL, NULL, false, NULL, 'bench_directeur', NULL, NULL, CURRENT_DATE - 4 + time '10:30', CURRENT_DATE - 4 + time '10:30'),
(305, 'Pâté de campagne', 'Pâté de campagne 180 g', '3000000000356', 1, 1, CURRENT_DATE - 5, 'dlc', 2, 'unité', 'Rayon frais', 15,
'valides', false, NULL, NULL, NULL, NULL, false, 'Retiré du rayon', 'bench_employee', 'bench_manager', CURRENT_DATE - 5 + time '08:00', CURRENT_DATE - 12 + time '08:30', CURRENT_DATE - 5 + time '08:00')`);
await q(`INSERT INTO customer_orders (id, order_taker, customer_name, customer_phone, customer_email, product_designation, product_reference, gencode,
quantity, supplier_id, status, deposit, is_promotional_price, customer_notified, notes, group_id, created_by, created_at, updated_at,
notified_at, notified_comment) VALUES
(300, 'Hugo', 'M. Colin', '06 66 66 66 66', NULL, 'Tronçonneuse', 'TR-35', '3245678901300', 1, 3,
'Arrivé', 20.00, false, false, NULL, 3, 'admin_local', CURRENT_DATE - 4 + time '12:00', CURRENT_DATE - 1 + time '12:00', NULL, NULL),
(301, 'Sophie', 'Mme Robert', '06 77 77 77 77', 'robert@client.test', 'Plateau de fromages', NULL, '3245678901317', 1, 1,
'prêt', 0.00, false, false, 'Pour samedi', 1, 'bench_employee', CURRENT_DATE - 2 + time '09:00', CURRENT_DATE + time '06:00', NULL, NULL),
(302, 'Léa', 'M. Simon', '06 88 88 88 88', NULL, 'Sécateur', 'SC-20', '3245678901324', 2, 4,
'Disponible', 0.00, false, NULL, NULL, 2, 'bench_directeur', CURRENT_DATE - 7 + time '09:00', CURRENT_DATE - 3 + time '09:00', NULL, NULL),
(303, 'Léa', 'Mme Michel', '06 99 99 99 99', NULL, 'Terreau 40 L', NULL, '3245678901331', 4, 4,
'Prêt', 5.50, true, false, NULL, 2, 'bench_directeur', CURRENT_DATE - 9 + time '09:00', CURRENT_DATE - 2 + time '09:00', NULL, NULL)`);
// 301 : créé l'an dernier (hors du compte qui numérote les nouveaux tickets),
// par un compte supprimé depuis (créateur absent de la jointure)
const year = new Date().getFullYear();
await q(`INSERT INTO sav_tickets (id, ticket_number, supplier_id, group_id, product_gencode, product_reference, product_designation, problem_type,
problem_description, resolution_description, status, priority, client_name, client_phone, created_by, created_at, updated_at, resolved_at, closed_at) VALUES
(300, $1, 2, 3, '3245678901300', 'TR-35', 'Tronçonneuse', 'autre', 'Chaîne bloquée', NULL, 'en_cours', 'critique', 'M. Colin', '06 66 66 66 66', 'admin_local', CURRENT_DATE - 3 + time '11:00', CURRENT_DATE - 1 + time '11:00', NULL, NULL),
(301, $2, 1, 1, '3000000000011', NULL, 'Yaourt nature', 'non_conforme', 'Emballage gonflé', 'Remboursé', 'ferme', 'normale', NULL, NULL, 'utilisateur_supprime', CURRENT_DATE - 400 + time '10:00', CURRENT_DATE - 390 + time '10:00', CURRENT_DATE - 395 + time '10:00', CURRENT_DATE - 390 + time '10:00'),
(302, $3, 4, 2, '3245678901324', 'SC-20', 'Sécateur', 'defectueux', 'Ressort cassé', NULL, 'attente_echange', 'faible', 'M. Simon', NULL, 'bench_directeur', CURRENT_DATE - 6 + time '14:00', CURRENT_DATE - 6 + time '14:00', NULL, NULL)`,
[`SAV-${year}-0004`, `SAV-${year - 1}-0042`, `SAV-${year}-0005`]);
}
+25
View File
@@ -0,0 +1,25 @@
// Lot 4, commandes et livraisons (cases/fournisseurs-commandes.ts) : cas que les
// données de base n'exercent pas.
type Query = (text: string, values?: unknown[]) => Promise<unknown>;
export async function seed(q: Query) {
// Commande de Metz dont le créateur n'existe plus (creator: null), en attente
// alors que sa livraison est livrée (synchronisation des statuts)
await q(`INSERT INTO orders (id, supplier_id, group_id, planned_date, quantity, unit, status, notes, created_by, created_at, updated_at) VALUES
(600, 2, 2, CURRENT_DATE + 5, 6, 'colis', 'pending', 'Créateur supprimé', 'utilisateur_supprime',
CURRENT_DATE - 2 + time '11:00', CURRENT_DATE - 2 + time '11:00')`);
await q(`INSERT INTO deliveries (id, order_id, supplier_id, group_id, scheduled_date, delivered_date, quantity, unit, status, notes, bl_number, bl_amount,
invoice_reference, invoice_amount, invoice_amount_ttc, due_date, reconciled, validated_at, control_validated, control_validated_by,
control_validated_at, created_by, created_at, updated_at) VALUES
-- Livraison de Nancy liée à la commande 600 de Metz : la commande n'est jamais jointe
(600, 600, 2, 1, CURRENT_DATE - 3, CURRENT_DATE - 3 + time '15:00', 6, 'colis', 'delivered', 'Commande d''un autre magasin', 'BL-6000', 120.00,
NULL, NULL, NULL, NULL, false, NULL, false, NULL, NULL, 'bench_manager', CURRENT_DATE - 4 + time '10:00', CURRENT_DATE - 3 + time '15:00'),
-- Bonduelle (rapprochement automatique), prévue, créateur supprimé ; même date
-- prévue que la livraison 7 (ordre à égalité de tri)
(601, NULL, 2, 1, CURRENT_DATE + 2, NULL, 3, 'colis', 'planned', NULL, NULL, NULL,
NULL, NULL, NULL, NULL, false, NULL, false, NULL, NULL, 'utilisateur_supprime', CURRENT_DATE - 1 + time '12:00', CURRENT_DATE - 1 + time '12:00'),
-- Bonduelle à Metz, rapprochée d'office, sans facture (API externe)
(602, NULL, 2, 2, CURRENT_DATE - 7, CURRENT_DATE - 7 + time '08:15', 4, 'colis', 'delivered', NULL, 'BL-6002', 88.40,
NULL, NULL, NULL, NULL, true, CURRENT_DATE - 7 + time '08:20', false, NULL, NULL, 'bench_directeur', CURRENT_DATE - 8 + time '10:00', CURRENT_DATE - 7 + time '08:20')`);
}
+63
View File
@@ -0,0 +1,63 @@
// Lot 4, rapprochement (cases/fournisseurs-rapprochement.ts) : avoirs et échéancier
// (identifiants 700…).
// - avoirs hors de Nancy : un de Metz à vérifier dans NocoDB, un d'Épinal (aucun
// compte du banc n'y est affecté, sauf l'administrateur) créé par un compte
// supprimé depuis, et un avoir validé à la référence vide ;
// - livraisons que GET /api/payment-schedule complète avec NocoDB : échéance
// absente (trouvée, introuvable, ou sans échéance dans NocoDB), montant TTC nul
// (repli « TTC »), magasin d'Épinal sans configuration NocoDB ;
// - échéances aux bornes du mois courant (premier jour à minuit, dernière
// microseconde, premier jour du mois suivant) pour l'export CSV.
type Query = (text: string, values?: unknown[]) => Promise<unknown>;
export async function seed(q: Query) {
await q(`INSERT INTO avoirs (id, supplier_id, group_id, invoice_reference, amount, comment, commercial_processed, status, webhook_sent, nocodb_verified,
nocodb_verified_at, processed_at, created_by, created_at, updated_at) VALUES
(700, 2, 2, 'FAC-2006', 10.50, 'Remise oubliée', false, 'Demandé', false, false, NULL, NULL, 'bench_directeur',
CURRENT_DATE - 3 + time '11:00', CURRENT_DATE - 3 + time '11:00'),
(701, 1, 3, NULL, NULL, NULL, NULL, 'En attente de demande', NULL, NULL, NULL, NULL, 'compte_disparu',
CURRENT_DATE - 2 + time '11:00', CURRENT_DATE - 2 + time '11:00'),
(702, 4, 1, ' ', 0.00, '', true, 'Reçu', true, true, CURRENT_DATE - 1 + time '09:00', CURRENT_DATE - 1 + time '09:00', 'bench_employee',
CURRENT_DATE - 1 + time '08:00', CURRENT_DATE - 1 + time '09:00')`);
const month = `date_trunc('month', CURRENT_DATE::timestamp)`;
await q(`INSERT INTO deliveries (id, order_id, supplier_id, group_id, scheduled_date, delivered_date, quantity, unit, status, notes, bl_number, bl_amount,
invoice_reference, invoice_amount, invoice_amount_ttc, due_date, reconciled, validated_at, control_validated, created_by, created_at, updated_at) VALUES
-- Échéance et montant absents : retrouvés dans NocoDB (FAC-2002)
(700, NULL, 2, 1, CURRENT_DATE - 8, CURRENT_DATE - 8 + time '10:00', 3, 'colis', 'delivered', NULL, 'BL-7000', 300.00,
'FAC-2002', NULL, NULL, NULL, false, CURRENT_DATE - 8 + time '10:10', false, 'bench_manager', CURRENT_DATE - 9 + time '10:00', CURRENT_DATE - 8 + time '10:10'),
-- Référence introuvable dans NocoDB
(701, NULL, 1, 1, CURRENT_DATE - 7, CURRENT_DATE - 7 + time '10:00', 1, 'colis', 'delivered', NULL, 'BL-7001', 50.00,
'FAC-INCONNUE', NULL, NULL, NULL, false, CURRENT_DATE - 7 + time '10:10', false, 'bench_manager', CURRENT_DATE - 8 + time '09:00', CURRENT_DATE - 7 + time '10:10'),
-- Trouvée sans échéance (avoir AV-3001) : seul le montant est repris
(702, NULL, 3, 1, CURRENT_DATE - 6, CURRENT_DATE - 6 + time '10:00', 1, 'colis', 'delivered', NULL, NULL, NULL,
'AV-3001', NULL, NULL, NULL, false, CURRENT_DATE - 6 + time '10:10', false, 'bench_manager', CURRENT_DATE - 7 + time '09:00', CURRENT_DATE - 6 + time '10:10'),
-- Montant TTC nul : repli « TTC », qui réécrit le HT et l'échéance de NocoDB (FAC-2006)
(703, NULL, 4, 1, CURRENT_DATE - 5, CURRENT_DATE - 5 + time '10:00', 2, 'palettes', 'delivered', NULL, 'BL-7003', 450.25,
'FAC-2006', 450.25, 0.00, CURRENT_DATE + 3 + time '00:00', false, CURRENT_DATE - 5 + time '10:10', false, 'bench_manager', CURRENT_DATE - 6 + time '09:00', CURRENT_DATE - 5 + time '10:10'),
-- Référence vide : hors échéancier
(704, NULL, 2, 1, CURRENT_DATE - 4, CURRENT_DATE - 4 + time '10:00', 1, 'colis', 'delivered', NULL, NULL, NULL,
'', 12.00, 14.40, CURRENT_DATE + 4, false, CURRENT_DATE - 4 + time '10:10', false, 'bench_manager', CURRENT_DATE - 5 + time '09:00', CURRENT_DATE - 4 + time '10:10'),
-- Metz : échéance absente, retrouvée dans NocoDB (FAC-2001)
(705, NULL, 1, 2, CURRENT_DATE - 4, CURRENT_DATE - 4 + time '11:00', 4, 'colis', 'delivered', NULL, 'BL-7005', 1200.00,
'FAC-2001', NULL, NULL, NULL, false, CURRENT_DATE - 4 + time '11:10', false, 'bench_directeur', CURRENT_DATE - 5 + time '11:00', CURRENT_DATE - 4 + time '11:10'),
-- Épinal : magasin sans configuration NocoDB
(706, NULL, 3, 3, CURRENT_DATE - 3, CURRENT_DATE - 3 + time '11:00', 1, 'palettes', 'delivered', NULL, 'BL-7006', 99.00,
'FAC-2004', NULL, NULL, NULL, false, CURRENT_DATE - 3 + time '11:10', false, 'admin_local', CURRENT_DATE - 4 + time '11:00', CURRENT_DATE - 3 + time '11:10'),
(707, NULL, 4, 3, CURRENT_DATE - 3, CURRENT_DATE - 3 + time '12:00', 1, 'palettes', 'delivered', NULL, 'BL-7007', 80.00,
'FAC-7007', 80.00, 96.00, ${month} + interval '12 days', false, CURRENT_DATE - 3 + time '12:10', false, 'admin_local', CURRENT_DATE - 4 + time '12:00', CURRENT_DATE - 3 + time '12:10'),
-- Bornes du mois courant (export CSV)
(708, NULL, 1, 1, CURRENT_DATE - 2, CURRENT_DATE - 2 + time '09:00', 1, 'colis', 'delivered', NULL, 'BL-7008', 100.00,
'FAC-7008', 100.00, 120.00, ${month}, false, CURRENT_DATE - 2 + time '09:10', false, 'bench_manager', CURRENT_DATE - 3 + time '08:00', CURRENT_DATE - 2 + time '09:10'),
(709, NULL, 2, 1, CURRENT_DATE - 2, CURRENT_DATE - 2 + time '10:00', 1, 'colis', 'delivered', NULL, 'BL-7009', 20.10,
'FAC-7009', 20.10, 24.12, ${month} + interval '1 month' - interval '1 microsecond', false, CURRENT_DATE - 2 + time '10:10', false, 'bench_manager', CURRENT_DATE - 3 + time '08:30', CURRENT_DATE - 2 + time '10:10'),
(710, NULL, 4, 1, CURRENT_DATE - 1, CURRENT_DATE - 1 + time '09:00', 1, 'colis', 'delivered', NULL, 'BL-7010', 55.55,
'FAC-7010', 55.55, 66.66, ${month} + interval '1 month', false, CURRENT_DATE - 1 + time '09:10', false, 'bench_manager', CURRENT_DATE - 2 + time '08:00', CURRENT_DATE - 1 + time '09:10'),
(711, NULL, 3, 1, CURRENT_DATE - 1, CURRENT_DATE - 1 + time '10:00', 1, 'colis', 'delivered', NULL, 'BL-7011', 0.10,
'FAC-7011', 0.10, 0.20, ${month} + interval '14 days 10:30', false, CURRENT_DATE - 1 + time '10:10', false, 'bench_manager', CURRENT_DATE - 2 + time '08:30', CURRENT_DATE - 1 + time '10:10'),
-- Échéances de NocoDB au format JJ/MM/AAAA (illisible par new Date) et MM/JJ/AAAA
(712, NULL, 2, 1, CURRENT_DATE - 1, CURRENT_DATE - 1 + time '11:00', 1, 'colis', 'delivered', NULL, 'BL-1007', 99.90,
'FAC-2007', NULL, NULL, NULL, false, CURRENT_DATE - 1 + time '11:10', false, 'bench_manager', CURRENT_DATE - 2 + time '09:00', CURRENT_DATE - 1 + time '11:10'),
(713, NULL, 3, 1, CURRENT_DATE - 1, CURRENT_DATE - 1 + time '12:00', 1, 'colis', 'delivered', NULL, 'BL-1008', 12.34,
'FAC-2008', NULL, NULL, NULL, true, CURRENT_DATE - 1 + time '12:10', false, 'bench_manager', CURRENT_DATE - 2 + time '09:30', CURRENT_DATE - 1 + time '12:10')`);
}
+23
View File
@@ -0,0 +1,23 @@
// Lot 4, services partagés (cases/fournisseurs-services.ts) : entrées du cache des
// factures pour exercer le marquage « rapproché » et le diagnostic du cache.
type Query = (text: string, values?: unknown[]) => Promise<unknown>;
export async function seed(q: Query) {
await q(`INSERT INTO invoice_verification_cache (id, cache_key, group_id, invoice_reference, supplier_name, invoice_amount, invoice_amount_ttc,
due_date, exists, match_type, error_message, cache_hit, api_call_time, is_reconciled, expires_at, created_at, updated_at) VALUES
-- Livraison 6 validée (Metz), entrée encore valable : marquée par la mise à jour en lot
(400, '2_fac-2006', 2, 'FAC-2006', 'Jardin & Co', 450.25, 540.30, CURRENT_DATE - 5, true, 'invoice_reference', NULL, false, 80, false,
CURRENT_DATE + 2, CURRENT_DATE - 3 + time '10:00', CURRENT_DATE - 3 + time '10:00'),
-- Numéro de BL de la livraison 8, validée : recherche infructueuse en cache
(401, '1_bl-0900', 1, 'BL-0900', NULL, NULL, NULL, NULL, false, 'none', 'Aucune correspondance trouvée dans NocoDB', false, NULL, false,
CURRENT_DATE + 1, CURRENT_DATE - 1 + time '09:00', CURRENT_DATE - 1 + time '09:00'),
-- Facture de la livraison 8, expirée : ni marquée, et supprimée à sa lecture
(402, '1_fac-1900', 1, 'FAC-1900', 'Lactalis', 800.00, 960.00, CURRENT_DATE - 350, true, 'invoice_reference', NULL, false, 70, false,
CURRENT_DATE - 1, CURRENT_DATE - 2 + time '09:00', CURRENT_DATE - 2 + time '09:00'),
-- Clé au format du diagnostic (référence_magasin), expirée : supprimée par GET /api/cache/diagnosis
(403, 'fac-2001_1', 1, 'FAC-2001', 'Lactalis', 1250.50, 1500.60, NULL, true, 'invoice_reference', NULL, false, NULL, false,
CURRENT_DATE - 2, CURRENT_DATE - 30 + time '09:00', CURRENT_DATE - 30 + time '09:00'),
-- Même format, valable : trouvée par le diagnostic
(404, 'fac-2006_2', 2, 'FAC-2006', 'Jardin & Co', 450.25, 540.30, NULL, true, 'invoice_reference', NULL, false, NULL, true,
CURRENT_DATE + 3, CURRENT_DATE - 30 + time '09:00', CURRENT_DATE - 30 + time '09:00')`);
}
+18
View File
@@ -0,0 +1,18 @@
// Données du lot 5 (administration) : des sauvegardes en plus de celle de
// seed.ts, pour que la liste soit tronquée à dix et que la création d'une
// sauvegarde fasse le ménage des plus anciennes (backupService.cleanOldBackups).
// banc-sauvegarde-500 a un fichier (sauvegardes/backup_banc_500.sql), copié
// par compare.ts dans le dossier de sauvegardes de chaque serveur, comme celui
// de banc-sauvegarde-1.
type Query = (text: string, values?: unknown[]) => Promise<unknown>;
export async function seed(q: Query) {
await q(`INSERT INTO database_backups (id, filename, description, size, created_at, created_by, tables_count, status, backup_type) VALUES
('banc-sauvegarde-500', 'backup_banc_500.sql', 'Manuel backup du banc', 64, CURRENT_DATE - 2 + time '14:00', 'admin_local', 26, 'completed', 'manual')`);
// Neuf anciennes sauvegardes automatiques, sans fichier, un jour d'écart chacune
for (let n = 1; n <= 9; n++) {
await q(`INSERT INTO database_backups (id, filename, description, size, created_at, created_by, tables_count, status, backup_type)
VALUES ($1, $2, 'Automatique backup du banc', $3, CURRENT_DATE - $4::int + time '02:00', 'system', 26, 'completed', 'automatic')`,
[`banc-sauvegarde-${500 + n}`, `backup_banc_${500 + n}.sql`, 1024 * n, 2 + n]);
}
}
@@ -0,0 +1,2 @@
-- Sauvegarde du banc (banc-sauvegarde-1)
SELECT 1;
@@ -0,0 +1,2 @@
-- Sauvegarde manuelle du banc (banc-sauvegarde-500)
SELECT 500;