refactor(backend): contributionEuro (Decimal) → contributionCents (Int) #217

Closed
opened 2026-07-03 21:00:01 +02:00 by niboer · 1 comment
Owner

Ziel

EventGuest.contributionEuro von Decimal(10,2) (Euro) auf Int (Cents) umstellen — konsistent zu FormFieldOption.price (bereits Cents).

Dieser Refaktor MUSS vor Issue #16 (ZUGFeRD-Rechnungsmodul) durchgefuehrt werden, da der Invoice-Run mit contributionCents arbeitet.

Betroffene Dateien (22 Files)


1. backend/prisma/schema.prisma (Z. 171)

// ALT:
contributionEuro Decimal   @default(0.00) @db.Decimal(10, 2) @map("contributionEuro")
// NEU:
contributionCents Int      @default(0) @map("contributionCents")

2. backend/prisma/migrations/ — neue Migration

ALTER TABLE "eventGuests" RENAME COLUMN "contributionEuro" TO "contributionCents";
ALTER TABLE "eventGuests" ALTER COLUMN "contributionCents" TYPE INTEGER USING ROUND("contributionCents" * 100)::int;
ALTER TABLE "eventGuests" ALTER COLUMN "contributionCents" SET DEFAULT 0;

3. backend/prisma/seed.ts (Z. 259, 267)

// ALT:
contributionEuro: 125.00,
contributionEuro: 180.00,
// NEU:
contributionCents: 12500,
contributionCents: 18000,

4. backend/prisma/migrations/0001_full_schema/migration.sql (Z. 124)

Einfach Überschreiben. Es gibt keinen alten Datenbestand.


5. backend/src/routes/forms.ts

Zeile Aenderung
8 contributionEuro: Number(guest.contributionEuro)contributionCents: guest.contributionCents
11-37 calculateContribution(): return totalCents / 100return totalCents
156 const contributionEuro = ...const contributionCents = ...
163 contributionEuro,contributionCents,
229 const contributionEuro = ...const contributionCents = ...
234 contributionEuro,contributionCents,
320 const contributionEuro = ...const contributionCents = ...
330 contributionEuro,contributionCents,
392 const contributionEuro = ...const contributionCents = ...
403 contributionEuro,contributionCents,
517-521 Number(guest.contributionEuro)guest.contributionCents, newEuro Vergleich + data: { contributionCents: newEuro }

6. backend/src/routes/guests.ts (Z. 11)

// ALT:
return { ...guest, contributionEuro: Number(guest.contributionEuro) };
// NEU:
return { ...guest, contributionCents: guest.contributionCents };

7. backend/src/routes/persons.ts

Zeile Aenderung
195 contributionEuro: inv.contributionEuro,contributionCents: inv.contributionCents,
737 contributionEuro: inv.contributionEuro,contributionCents: inv.contributionCents,

8. backend/openapi.yaml

Zeile Schema Aenderung
318-320 MeSummary → invitations[].contributionEuro type: number, format: floattype: integer, minimum: 0
523-525 Invitation → contributionEuro type: number, format: floattype: integer, minimum: 0
662-664 UpdateGuestPayment → contributionEuro type: number, format: floattype: integer, minimum: 0
3191 recalculateContributions-description contributionEurocontributionCents

Dazu kommt: alle drei contributionEuro heissen neu contributionCents im Property-Key.


9. backend/src/__tests__/forms.test.ts

Alle contributionEuro: 25.0contributionCents: 2500; 00; 505000.

Zeile(n) ALT NEU
141 contributionEuro: 25.0 contributionCents: 2500
209 contributionEuro: 25.0 contributionCents: 2500
272 contributionEuro: 0 contributionCents: 0
296 contributionEuro: 0 contributionCents: 0
311 contributionEuro: 0 contributionCents: 0
337 contributionEuro: 50 contributionCents: 5000
352 contributionEuro: 0 contributionCents: 0
408 contributionEuro: 0 contributionCents: 0
486 contributionEuro: 25.0 contributionCents: 2500

10. backend/src/__tests__/guests.test.ts

Zeile(n) ALT NEU
295 contributionEuro: 25.0 contributionCents: 2500
325 contributionEuro: 25.0 contributionCents: 2500
370 contributionEuro: 25.0 contributionCents: 2500
398 contributionEuro: 25.0 contributionCents: 2500
426 contributionEuro: 25.0 contributionCents: 2500
499 contributionEuro: 25.0 contributionCents: 2500
558 contributionEuro: 0.0 contributionCents: 0
571 expect(res.json().contributionEuro).toBe(0.0) expect(res.json().contributionCents).toBe(0)

11. backend/src/__tests__/integration.test.ts

Zeile ALT NEU
129 contributionEuro: '0' contributionCents: '0'
157 contributionEuro: 25.0 contributionCents: 2500
185 contributionEuro: 25.0 contributionCents: 2500
211 contributionEuro: 25.0 contributionCents: 2500

12. backend/src/__tests__/persons.test.ts

Zeile ALT NEU
943 contributionEuro: 25.0 contributionCents: 2500

13. frontend/src/lib/api/generated.ts (4 Stellen)

Zeile ALT NEU
184 contributionEuro?: number; contributionCents?: number;
314 contributionEuro?: number; contributionCents?: number;
394 contributionEuro?: number; contributionCents?: number;
4011 * Berechnet contributionEuro fuer alle Gaeste ... contributionCents

Hinweis: Wird nach OpenAPI-Aenderung via npm run generate:api (Orval) regeneriert. Nicht manuell aendern.


14. frontend/src/routes/app/events/[eventId]/+page.svelte

Zeile ALT NEU
675 (selfGuest.contributionEuro ?? 0).toLocaleString(...) ((selfGuest.contributionCents ?? 0) / 100).toLocaleString(...)
701 (selfGuest.contributionEuro ?? 0).toLocaleString(...) ((selfGuest.contributionCents ?? 0) / 100).toLocaleString(...)
908 { key: 'contributionEuro', label: 'Beitrag', ... } { key: 'contributionCents', label: 'Beitrag', ... }
926 formatEuro((row as Guest).contributionEuro) formatEuroCent((row as Guest).contributionCents)
1113 { key: 'contributionEuro', label: 'Beitrag', ... } { key: 'contributionCents', label: 'Beitrag', ... }
1127 formatEuro((row as Guest).contributionEuro) formatEuroCent((row as Guest).contributionCents)

15. frontend/src/routes/app/events/[eventId]/+page.server.ts (Z. 140-148)

// ALT (4x):
.reduce((sum, g) => sum + Number(g.contributionEuro || 0), 0);
// NEU (4x):
.reduce((sum, g) => sum + (g.contributionCents || 0), 0);

16. frontend/src/tests/events.test.ts (Z. 234-235)

// ALT:
{ ... contributionEuro: 25 },
{ ... contributionEuro: 25 },
// NEU:
{ ... contributionCents: 2500 },
{ ... contributionCents: 2500 },

17. frontend/src/tests/guests.test.ts

Zeile ALT NEU
6 contributionEuro: 25.0 contributionCents: 2500
154 contributionEuro: 50.0 contributionCents: 5000
155 contributionEuro: 20.0 contributionCents: 2000
156 contributionEuro: 25.0 contributionCents: 2500
248 contributionEuro: '30' contributionCents: '3000'
285 contributionEuro: '25' contributionCents: '2500'

18. frontend/src/tests/self-service.test.ts (Z. 9-10)

// ALT:
{ ... contributionEuro: 25 },
{ ... contributionEuro: 50 },
// NEU:
{ ... contributionCents: 2500 },
{ ... contributionCents: 5000 },

19. docs/design/02-datenmodell.md (Z. 76, 79)

// ALT:
Der Beitrag wird als Dezimalwert in `eventGuests.contributionEuro` gespeichert.
// NEU:
Der Beitrag wird als Integer (Cents) in `eventGuests.contributionCents` gespeichert.

// ALT:
aktualisiert `contributionEuro`
// NEU:
aktualisiert `contributionCents`

20. PROJECT_STATE.md (Z. 51)

Keine Aenderung noetig (referenziert nur Issue #184, nicht das Feld selbst).


Test-Validierung

Nach der Migration:

  1. npm run test (Backend) — alle Tests muessen gruen sein
  2. npm run test (Frontend) — alle Tests muessen gruen sein
  3. npm run generate:api (Frontend) — Orval regeneriert generated.ts
  4. Prisma-Kompilierung: npx prisma generate muss fehlerfrei sein

Abhaengigkeiten

Blockiert #16 (ZUGFeRD-Rechnungsmodul). Dieser Refaktor MUSS vor #16 gemergt werden.

Labels

  • component/backend
  • component/frontend
  • type/refactor
  • prio/high
## Ziel `EventGuest.contributionEuro` von `Decimal(10,2)` (Euro) auf `Int` (Cents) umstellen — konsistent zu `FormFieldOption.price` (bereits Cents). Dieser Refaktor MUSS vor Issue #16 (ZUGFeRD-Rechnungsmodul) durchgefuehrt werden, da der Invoice-Run mit `contributionCents` arbeitet. ## Betroffene Dateien (22 Files) --- ### 1. `backend/prisma/schema.prisma` (Z. 171) ```prisma // ALT: contributionEuro Decimal @default(0.00) @db.Decimal(10, 2) @map("contributionEuro") // NEU: contributionCents Int @default(0) @map("contributionCents") ``` --- ### 2. `backend/prisma/migrations/` — neue Migration ```sql ALTER TABLE "eventGuests" RENAME COLUMN "contributionEuro" TO "contributionCents"; ALTER TABLE "eventGuests" ALTER COLUMN "contributionCents" TYPE INTEGER USING ROUND("contributionCents" * 100)::int; ALTER TABLE "eventGuests" ALTER COLUMN "contributionCents" SET DEFAULT 0; ``` --- ### 3. `backend/prisma/seed.ts` (Z. 259, 267) ```typescript // ALT: contributionEuro: 125.00, contributionEuro: 180.00, // NEU: contributionCents: 12500, contributionCents: 18000, ``` --- ### 4. `backend/prisma/migrations/0001_full_schema/migration.sql` (Z. 124) Einfach Überschreiben. Es gibt keinen alten Datenbestand. --- ### 5. `backend/src/routes/forms.ts` | Zeile | Aenderung | |-------|-----------| | 8 | `contributionEuro: Number(guest.contributionEuro)` → `contributionCents: guest.contributionCents` | | 11-37 | `calculateContribution()`: `return totalCents / 100` → `return totalCents` | | 156 | `const contributionEuro = ...` → `const contributionCents = ...` | | 163 | `contributionEuro,` → `contributionCents,` | | 229 | `const contributionEuro = ...` → `const contributionCents = ...` | | 234 | `contributionEuro,` → `contributionCents,` | | 320 | `const contributionEuro = ...` → `const contributionCents = ...` | | 330 | `contributionEuro,` → `contributionCents,` | | 392 | `const contributionEuro = ...` → `const contributionCents = ...` | | 403 | `contributionEuro,` → `contributionCents,` | | 517-521 | `Number(guest.contributionEuro)` → `guest.contributionCents`, `newEuro` Vergleich + `data: { contributionCents: newEuro }` | --- ### 6. `backend/src/routes/guests.ts` (Z. 11) ```typescript // ALT: return { ...guest, contributionEuro: Number(guest.contributionEuro) }; // NEU: return { ...guest, contributionCents: guest.contributionCents }; ``` --- ### 7. `backend/src/routes/persons.ts` | Zeile | Aenderung | |-------|-----------| | 195 | `contributionEuro: inv.contributionEuro,` → `contributionCents: inv.contributionCents,` | | 737 | `contributionEuro: inv.contributionEuro,` → `contributionCents: inv.contributionCents,` | --- ### 8. `backend/openapi.yaml` | Zeile | Schema | Aenderung | |-------|--------|-----------| | 318-320 | MeSummary → `invitations[].contributionEuro` | `type: number, format: float` → `type: integer, minimum: 0` | | 523-525 | Invitation → `contributionEuro` | `type: number, format: float` → `type: integer, minimum: 0` | | 662-664 | UpdateGuestPayment → `contributionEuro` | `type: number, format: float` → `type: integer, minimum: 0` | | 3191 | `recalculateContributions`-description | `contributionEuro` → `contributionCents` | Dazu kommt: alle drei `contributionEuro` heissen neu `contributionCents` im Property-Key. --- ### 9. `backend/src/__tests__/forms.test.ts` Alle `contributionEuro: 25.0` → `contributionCents: 2500`; `0` → `0`; `50` → `5000`. | Zeile(n) | ALT | NEU | |-----------|-----|-----| | 141 | `contributionEuro: 25.0` | `contributionCents: 2500` | | 209 | `contributionEuro: 25.0` | `contributionCents: 2500` | | 272 | `contributionEuro: 0` | `contributionCents: 0` | | 296 | `contributionEuro: 0` | `contributionCents: 0` | | 311 | `contributionEuro: 0` | `contributionCents: 0` | | 337 | `contributionEuro: 50` | `contributionCents: 5000` | | 352 | `contributionEuro: 0` | `contributionCents: 0` | | 408 | `contributionEuro: 0` | `contributionCents: 0` | | 486 | `contributionEuro: 25.0` | `contributionCents: 2500` | --- ### 10. `backend/src/__tests__/guests.test.ts` | Zeile(n) | ALT | NEU | |-----------|-----|-----| | 295 | `contributionEuro: 25.0` | `contributionCents: 2500` | | 325 | `contributionEuro: 25.0` | `contributionCents: 2500` | | 370 | `contributionEuro: 25.0` | `contributionCents: 2500` | | 398 | `contributionEuro: 25.0` | `contributionCents: 2500` | | 426 | `contributionEuro: 25.0` | `contributionCents: 2500` | | 499 | `contributionEuro: 25.0` | `contributionCents: 2500` | | 558 | `contributionEuro: 0.0` | `contributionCents: 0` | | 571 | `expect(res.json().contributionEuro).toBe(0.0)` | `expect(res.json().contributionCents).toBe(0)` | --- ### 11. `backend/src/__tests__/integration.test.ts` | Zeile | ALT | NEU | |-------|-----|-----| | 129 | `contributionEuro: '0'` | `contributionCents: '0'` | | 157 | `contributionEuro: 25.0` | `contributionCents: 2500` | | 185 | `contributionEuro: 25.0` | `contributionCents: 2500` | | 211 | `contributionEuro: 25.0` | `contributionCents: 2500` | --- ### 12. `backend/src/__tests__/persons.test.ts` | Zeile | ALT | NEU | |-------|-----|-----| | 943 | `contributionEuro: 25.0` | `contributionCents: 2500` | --- ### 13. `frontend/src/lib/api/generated.ts` (4 Stellen) | Zeile | ALT | NEU | |-------|-----|-----| | 184 | `contributionEuro?: number;` | `contributionCents?: number;` | | 314 | `contributionEuro?: number;` | `contributionCents?: number;` | | 394 | `contributionEuro?: number;` | `contributionCents?: number;` | | 4011 | `* Berechnet contributionEuro fuer alle Gaeste ...` | `contributionCents` | **Hinweis:** Wird nach OpenAPI-Aenderung via `npm run generate:api` (Orval) regeneriert. Nicht manuell aendern. --- ### 14. `frontend/src/routes/app/events/[eventId]/+page.svelte` | Zeile | ALT | NEU | |-------|-----|-----| | 675 | `(selfGuest.contributionEuro ?? 0).toLocaleString(...)` | `((selfGuest.contributionCents ?? 0) / 100).toLocaleString(...)` | | 701 | `(selfGuest.contributionEuro ?? 0).toLocaleString(...)` | `((selfGuest.contributionCents ?? 0) / 100).toLocaleString(...)` | | 908 | `{ key: 'contributionEuro', label: 'Beitrag', ... }` | `{ key: 'contributionCents', label: 'Beitrag', ... }` | | 926 | `formatEuro((row as Guest).contributionEuro)` | `formatEuroCent((row as Guest).contributionCents)` | | 1113 | `{ key: 'contributionEuro', label: 'Beitrag', ... }` | `{ key: 'contributionCents', label: 'Beitrag', ... }` | | 1127 | `formatEuro((row as Guest).contributionEuro)` | `formatEuroCent((row as Guest).contributionCents)` | --- ### 15. `frontend/src/routes/app/events/[eventId]/+page.server.ts` (Z. 140-148) ```typescript // ALT (4x): .reduce((sum, g) => sum + Number(g.contributionEuro || 0), 0); // NEU (4x): .reduce((sum, g) => sum + (g.contributionCents || 0), 0); ``` --- ### 16. `frontend/src/tests/events.test.ts` (Z. 234-235) ```typescript // ALT: { ... contributionEuro: 25 }, { ... contributionEuro: 25 }, // NEU: { ... contributionCents: 2500 }, { ... contributionCents: 2500 }, ``` --- ### 17. `frontend/src/tests/guests.test.ts` | Zeile | ALT | NEU | |-------|-----|-----| | 6 | `contributionEuro: 25.0` | `contributionCents: 2500` | | 154 | `contributionEuro: 50.0` | `contributionCents: 5000` | | 155 | `contributionEuro: 20.0` | `contributionCents: 2000` | | 156 | `contributionEuro: 25.0` | `contributionCents: 2500` | | 248 | `contributionEuro: '30'` | `contributionCents: '3000'` | | 285 | `contributionEuro: '25'` | `contributionCents: '2500'` | --- ### 18. `frontend/src/tests/self-service.test.ts` (Z. 9-10) ```typescript // ALT: { ... contributionEuro: 25 }, { ... contributionEuro: 50 }, // NEU: { ... contributionCents: 2500 }, { ... contributionCents: 5000 }, ``` --- ### 19. `docs/design/02-datenmodell.md` (Z. 76, 79) ```markdown // ALT: Der Beitrag wird als Dezimalwert in `eventGuests.contributionEuro` gespeichert. // NEU: Der Beitrag wird als Integer (Cents) in `eventGuests.contributionCents` gespeichert. // ALT: aktualisiert `contributionEuro` // NEU: aktualisiert `contributionCents` ``` --- ### 20. `PROJECT_STATE.md` (Z. 51) Keine Aenderung noetig (referenziert nur Issue #184, nicht das Feld selbst). --- ## Test-Validierung Nach der Migration: 1. `npm run test` (Backend) — alle Tests muessen gruen sein 2. `npm run test` (Frontend) — alle Tests muessen gruen sein 3. `npm run generate:api` (Frontend) — Orval regeneriert generated.ts 4. Prisma-Kompilierung: `npx prisma generate` muss fehlerfrei sein ## Abhaengigkeiten Blockiert #16 (ZUGFeRD-Rechnungsmodul). Dieser Refaktor MUSS vor #16 gemergt werden. ## Labels - `component/backend` - `component/frontend` - `type/refactor` - `prio/high`
Author
Owner

Implementierung abgeschlossen auf Branch issue-217.

Commits:

  • 0e6dfb1 — Backend: Schema, Migration, Seed, Routes, openapi.yaml, Tests, Design-Dokument
  • eb2e31f — Frontend: Orval-Regeneration, page.svelte, page.server.ts, Tests

PR #237 kann gemergt werden, sobald ein Actions-Runner verfuegbar ist oder manuell via Web-UI bei aufgehobenem Branch-Protection.

Implementierung abgeschlossen auf Branch `issue-217`. Commits: - `0e6dfb1` — Backend: Schema, Migration, Seed, Routes, openapi.yaml, Tests, Design-Dokument - `eb2e31f` — Frontend: Orval-Regeneration, page.svelte, page.server.ts, Tests PR #237 kann gemergt werden, sobald ein Actions-Runner verfuegbar ist oder manuell via Web-UI bei aufgehobenem Branch-Protection.
Sign in to join this conversation.
No description provided.