feat: ajouter le litige par un clic et isoler les sauvegardes du build
Some checks failed
Validation applicative / TypeScript, tests et build (push) Failing after 1m51s

This commit is contained in:
Manus
2026-11-03 14:51:56 +00:00
parent a6d90a90c3
commit 478b069fcd
3 changed files with 51 additions and 1 deletions

View File

@@ -9,3 +9,17 @@ client/dist
README.md README.md
uploads uploads
storage storage
# Les sauvegardes et secrets ne doivent jamais entrer dans une image de build.
**/backups
**/*backup*
**/.ssh
**/.manus-logs
**/__pycache__
**/*.pyc
**/*.sql.gz
**/*.bundle
**/*.zip
**/*.tar
**/*.tar.gz
**/*.pem
**/*.key

View File

@@ -66,6 +66,21 @@ export default function InvoiceDetail() {
const upsertLearningMutation = trpc.learnings.upsert.useMutation(); const upsertLearningMutation = trpc.learnings.upsert.useMutation();
// Le premier clic ajoute le litige, puis ouvre la saisie du commentaire.
const markDisputeMutation = trpc.invoices.setDispute.useMutation({
onSuccess: async () => {
utils.invoices.getById.setData({ id: invoiceId }, current => current ? { ...current, isDisputed: 1 } : current);
setDisputeDialogOpen(true);
toast.success("Facture ajoutée aux litiges");
await Promise.all([
utils.invoices.getById.invalidate({ id: invoiceId }),
utils.invoices.list.invalidate(),
utils.invoices.listDisputes.invalidate(),
]);
},
onError: error => toast.error(error.message || "Impossible d’ajouter cette facture aux litiges"),
});
const updateMutation = trpc.invoices.update.useMutation({ const updateMutation = trpc.invoices.update.useMutation({
onSuccess: () => { onSuccess: () => {
toast.success("Facture mise à jour avec succès"); toast.success("Facture mise à jour avec succès");
@@ -198,7 +213,10 @@ export default function InvoiceDetail() {
</div> </div>
</div> </div>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
<Button variant="outline" onClick={() => setDisputeDialogOpen(true)} className="border-amber-200 bg-amber-50 text-amber-800 hover:bg-amber-100"> <Button variant="outline" disabled={markDisputeMutation.isPending} onClick={() => {
if (canEdit && invoice.isDisputed !== 1) markDisputeMutation.mutate({ id: invoiceId, isDisputed: true });
else setDisputeDialogOpen(true);
}} className="border-amber-200 bg-amber-50 text-amber-800 hover:bg-amber-100">
<Flag className="w-4 h-4 mr-2" aria-hidden="true" /> <Flag className="w-4 h-4 mr-2" aria-hidden="true" />
Litige Litige
{invoice.isDisputed === 1 ? <Badge className="ml-2 bg-amber-100 text-amber-800 hover:bg-amber-100">En cours</Badge> : null} {invoice.isDisputed === 1 ? <Badge className="ml-2 bg-amber-100 text-amber-800 hover:bg-amber-100">En cours</Badge> : null}

View File

@@ -0,0 +1,18 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
describe("Déploiement et action directe Litige", () => {
it("ajoute le litige dès le clic sur le bouton avant de proposer le commentaire", () => {
const detail = readFileSync(join(process.cwd(), "client/src/pages/InvoiceDetail.tsx"), "utf8");
expect(detail).toContain('markDisputeMutation.mutate({ id: invoiceId, isDisputed: true })');
expect(detail).toContain('canEdit && invoice.isDisputed !== 1');
});
it("exclut les sauvegardes et les fichiers d’exploitation sans exclure les migrations SQL", () => {
const patterns = readFileSync(join(process.cwd(), ".dockerignore"), "utf8").split("\n");
expect(patterns).toContain('**/backups');
expect(patterns).toContain('**/*.sql.gz');
expect(patterns).toContain('**/*.bundle');
expect(patterns).not.toContain('**/*.sql');
expect(patterns).not.toContain('drizzle');
});
});