diff --git a/.claude/settings.local.json b/.claude/settings.local.json index a3da492..ef71591 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -2,7 +2,10 @@ "permissions": { "allow": [ "Bash(pnpm -F api typecheck)", - "Bash(pnpm -F @rubis/web typecheck)" + "Bash(pnpm -F @rubis/web typecheck)", + "Bash(rtk grep *)", + "Bash(rtk node *)", + "Bash(rtk pnpm *)" ] } } diff --git a/apps/api/app/controllers/checkin_controller.ts b/apps/api/app/controllers/checkin_controller.ts index 141c5f4..5da66ab 100644 --- a/apps/api/app/controllers/checkin_controller.ts +++ b/apps/api/app/controllers/checkin_controller.ts @@ -100,7 +100,7 @@ export default class CheckinController { await recordActivity({ organizationId: invoice.organizationId, kind: 'invoice_paid', - label: `Facture ${invoice.numero} marquée encaissée via check-in`, + label: `Facture ${invoice.numero} marquée encaissée via confirmation`, meta: { invoiceId: invoice.id, clientId: invoice.clientId }, trx, }) diff --git a/apps/api/app/controllers/invoices_controller.ts b/apps/api/app/controllers/invoices_controller.ts index ceec1b4..61f23e5 100644 --- a/apps/api/app/controllers/invoices_controller.ts +++ b/apps/api/app/controllers/invoices_controller.ts @@ -13,6 +13,7 @@ import { cancelFutureRelances } from '#services/relance_scheduler' import { scheduleCheckinForInvoice, cancelCheckinForInvoice } from '#services/checkin_scheduler' import logger from '@adonisjs/core/services/logger' import * as clock from '#services/clock' +import drive from '@adonisjs/drive/services/main' const PAGE_SIZE = 50 @@ -95,13 +96,16 @@ function buildTimeline( } else state = 'future' const subject = step.subject.replace('{{numero}}', invoice.numero) + // Wording uniforme et rassurant : aucune relance ne part sans que l'user + // confirme l'impayé. On évite "programmé" tout court qui sonne comme + // "ça va partir tout seul". const what = task ? task.status === 'sent' - ? `Email envoyé · "${subject}"` - : `Email programmé · "${subject}"` - : invoice.status === 'pending' - ? `À programmer après check-in · "${subject}"` - : `Relance non programmée · "${subject}"` + ? `Envoyée après votre confirmation · "${subject}"` + : task.status === 'cancelled' + ? `Annulée — facture encaissée · "${subject}"` + : `Confirmation avant envoi · "${subject}"` + : `Confirmation avant envoi · "${subject}"` events.push({ id: `${invoice.id}__step_${step.order}`, @@ -325,6 +329,51 @@ export default class InvoicesController { return response.status(201).json({ data: serializeInvoice(invoice) }) } + /** + * GET /invoices/:id/pdf — stream le PDF/image originel de la facture. + * + * Source : `pdfStorageKey` propagé depuis le draft d'import lors de la + * validation. 404 si la facture n'a pas de fichier (saisie manuelle). + * Auth : Bearer (vérifié sur l'org). Le SPA fetch via api.fetchBlob + * puis affiche dans un