feat: add receipt upload functionality for Einnahmen and Beleg routes
Build and Push Docker Image / build (push) Successful in 1m34s
Build and Push Docker Image / build (push) Successful in 1m34s
- Implemented upload and retrieval of receipts (Belege) associated with Einnahmen entries. - Added new API routes for uploading and deleting receipts. - Updated the Einnahmen model to include a `belegUrl` field for storing receipt references. - Enhanced the Einnahmen page to support file uploads and display existing receipts. - Introduced drag-and-drop functionality for file uploads and improved user feedback during uploads. - Added necessary validation for file types and sizes during uploads.
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
# Copilot Instructions — Annas Rechnungsmanager
|
||||
|
||||
German accounting & invoice management system for tax consultants (Steuerberater), built with React Router v7 (SSR), Prisma, MariaDB, and Tailwind CSS v4.
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
npm run dev # Dev server at http://localhost:5173
|
||||
npm run build # Production build
|
||||
npm run typecheck # react-router typegen + tsc (run before every commit)
|
||||
npm run lint # ESLint
|
||||
|
||||
npm run db:migrate # prisma migrate dev (create + apply migration)
|
||||
npm run db:seed # Seed database with sample data
|
||||
npm run db:studio # Prisma Studio GUI
|
||||
|
||||
npm run setup-admin # Create initial admin user
|
||||
npm run reset-password
|
||||
```
|
||||
|
||||
**Required `.env` variables:**
|
||||
```env
|
||||
DATABASE_URL="mysql://root:password@localhost:3306/annas_rechnungsmanager"
|
||||
AUTH_SECRET="<random 32-byte hex>" # NOT SESSION_SECRET
|
||||
NODE_ENV="development"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### Route Layout
|
||||
|
||||
Routes are **explicitly configured** in `app/routes.ts` (not auto-discovered by file name). Two layout wrappers exist:
|
||||
- `dashboard-layout.tsx` — wraps all authenticated company/user routes
|
||||
- `admin-layout.tsx` — wraps `/admin/*` (requires ADMIN role)
|
||||
|
||||
**UI routes** (`app/routes/companies.*.tsx`, etc.) export `loader` + default component.
|
||||
**API routes** (`app/routes/api.*.ts`) export only `action` (no default export, no loader).
|
||||
|
||||
### Path Alias
|
||||
|
||||
`@/` resolves to `app/`. Use it everywhere: `import prisma from "@/lib/prisma.server"`.
|
||||
|
||||
### Data Flow Pattern
|
||||
|
||||
```
|
||||
UI Route (loader/action)
|
||||
→ app/lib/ (business logic + Prisma queries)
|
||||
→ prisma.server.ts (single Prisma Client instance)
|
||||
→ AuditLog (mandatory on every write)
|
||||
```
|
||||
|
||||
Auth helpers live in `app/session.server.ts`:
|
||||
- `requireUser(request)` — throws redirect to `/login` if not authenticated
|
||||
- `requireAdmin(request)` — throws redirect to `/` if not ADMIN
|
||||
- `getApiUser(request)` — returns user or null (for API routes)
|
||||
|
||||
---
|
||||
|
||||
## Key Conventions
|
||||
|
||||
### Input Validation
|
||||
|
||||
All Zod schemas live in `app/lib/schemas.ts`. Reusable validators (`currencySchema`, `taxRateSchema`, `ibanSchema`, `vatIdSchema`) are defined there — import and extend them, don't redefine.
|
||||
|
||||
API routes must `safeParse` the request body before any DB access:
|
||||
```ts
|
||||
const parsed = mySchema.safeParse(await request.json());
|
||||
if (!parsed.success) return Response.json({ error: parsed.error.issues }, { status: 400 });
|
||||
```
|
||||
|
||||
### Tax Calculations (Never Trust the Client)
|
||||
|
||||
Always **recalculate amounts server-side** using `app/lib/tax.ts`:
|
||||
- `calcItemAmounts(quantity, unitPrice, taxRate)` — standard MwSt.
|
||||
- `calcItemAmountsKleinunternehmer(quantity, unitPrice)` — §19 UStG, no VAT
|
||||
- `calcInvoiceTotals(items)` — sums net/tax/gross
|
||||
|
||||
Valid tax rates: `0`, `7`, `19` (enforced by `taxRateSchema`).
|
||||
|
||||
The `Company.kleinunternehmer` flag controls which calculation is used — always read it from the DB, not from the request body.
|
||||
|
||||
### Audit Logging (Mandatory on Every Write)
|
||||
|
||||
Every mutation must call `log()` from `app/lib/logger.server.ts`. The `action` parameter must use the `LogAction` union type defined in that file:
|
||||
```ts
|
||||
await log({ userId: user.id, action: "CREATE_INVOICE", entity: "Invoice", entityId: invoice.id, metadata: {...}, request });
|
||||
```
|
||||
Logging failures are swallowed intentionally — never let them break the main operation.
|
||||
|
||||
### Database Rules
|
||||
|
||||
- All Prisma queries belong in `app/lib/`, not in routes or components.
|
||||
- Use `prisma.$transaction()` for multi-step operations (e.g., creating an invoice + a Buchung entry).
|
||||
- Invoice amounts are stored as both net/tax/gross (`Decimal(10,2)`) — always persist all three.
|
||||
- Soft-delete pattern: invoices use `deletedAt` field; companies use `archived`/`archivedAt`.
|
||||
- When an invoice is marked PAID, a linked `Buchung` record is created automatically.
|
||||
|
||||
### Domain Vocabulary (German ↔ Code)
|
||||
|
||||
| German | Code / Table |
|
||||
|--------|--------------|
|
||||
| Mandant | `Company` model |
|
||||
| Buchung | `Buchung` model (transaction/ledger entry) |
|
||||
| Anlagegut | `Anlagegut` model (fixed asset) |
|
||||
| Ausgabe | expense (type `ENTNAHME` in `Buchung`) |
|
||||
| Einnahme | revenue (type `EINLAGE` in `Buchung`) |
|
||||
| Kleinunternehmer | §19 UStG — no VAT on invoices |
|
||||
| AFA | Absetzung für Abnutzung — depreciation, computed in `app/lib/afa.ts` |
|
||||
|
||||
### Sessions
|
||||
|
||||
Session cookie name: `__session`, expires after **4 hours**, stored via `createCookieSessionStorage`. Keys stored in session: `userId`, `userName`, `userRole`.
|
||||
|
||||
---
|
||||
|
||||
## High-Impact Files
|
||||
|
||||
Changes to these files have wide blast radius — check dependents carefully:
|
||||
|
||||
| File | Why it matters |
|
||||
|------|----------------|
|
||||
| `app/session.server.ts` | Auth used by every protected route |
|
||||
| `app/lib/tax.ts` | Tax calculations used in invoices, reports, exports |
|
||||
| `app/lib/afa.ts` | Depreciation logic used in reports and asset management |
|
||||
| `prisma/schema.prisma` | Any change requires a migration |
|
||||
| `app/routes.ts` | Route config — adding routes here is required, not automatic |
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"playwright": {
|
||||
"command": "npx",
|
||||
"args": ["@playwright/mcp@latest", "--browser", "chromium"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
name: "Copilot Setup Steps"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- .github/workflows/copilot-setup-steps.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/copilot-setup-steps.yml
|
||||
|
||||
jobs:
|
||||
copilot-setup-steps:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: "npm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: npx playwright install --with-deps chromium
|
||||
Reference in New Issue
Block a user