excel preview & file handling

This commit is contained in:
richardtekula
2025-11-24 10:18:28 +01:00
parent dfcf8056f3
commit 7fd6b9e742
12 changed files with 1336 additions and 18 deletions

View File

@@ -1,7 +1,7 @@
# 📚 CRM SERVER - KOMPLETNÁ DOKUMENTÁCIA
> Vytvorené: 2025-11-21
> Verzia: 1.0
> Verzia: 1.1
> Backend: Node.js + Express + Drizzle ORM + PostgreSQL
> Frontend: React
@@ -30,6 +30,7 @@
- **Email:** JMAP protocol (Truemail.sk)
- **Encryption:** AES-256-GCM (email passwords)
- **File Upload:** Multer
- **Time Tracking:** Real-time timer s automatickým stop/start
### Databázové Tabuľky
```
@@ -42,6 +43,8 @@ users → userEmailAccounts ← emailAccounts
companies → projects → todos → notes
timesheets
users → timeEntries → projects/todos/companies
```
**Vzťahy:**
@@ -49,6 +52,8 @@ companies → projects → todos → notes
- `companies``projects`: One-to-many (company môže mať viac projektov)
- `projects``todos`, `notes`, `timesheets`: One-to-many
- `users``todos.assignedTo`: One-to-many (user má assigned úlohy)
- `users``timeEntries`: One-to-many (user má záznamy času)
- `timeEntries``projects`, `todos`, `companies`: Optional Many-to-one
---
@@ -497,7 +502,81 @@ validateJmapCredentials(email, password)
---
### 11. audit.service.js
### 11. time-tracking.service.js
**Účel:** Sledovanie odpracovaného času
**Databáza:** `timeEntries`, `projects`, `todos`, `companies`, `users`
**Metódy:**
```javascript
startTimeEntry(userId, data)
Validate: project/todo/company exists (ak poskytnuté)
Check: existujúci bežiaci časovač
Ak beží automaticky ho zastaví
INSERT INTO timeEntries
isRunning = true, startTime = NOW()
stopTimeEntry(entryId, userId, data)
Validate: ownership, isRunning = true
Počíta duration v minútach
UPDATE timeEntries SET endTime, duration, isRunning = false
Optional: update projectId, todoId, companyId, description
getRunningTimeEntry(userId)
SELECT * WHERE userId AND isRunning = true
Vráti aktuálny bežiaci časovač (alebo null)
getAllTimeEntries(userId, filters)
Filter: projectId, todoId, companyId, startDate, endDate
SELECT * FROM timeEntries WHERE userId
ORDER BY startTime DESC
getMonthlyTimeEntries(userId, year, month)
SELECT * WHERE userId AND startTime BETWEEN month range
Používa sa pre mesačný prehľad
getTimeEntryById(entryId)
SELECT * WHERE id
Throw NotFoundError ak neexistuje
getTimeEntryWithRelations(entryId)
LEFT JOIN project, todo, company
Vráti všetko v jednom objekte
updateTimeEntry(entryId, userId, data)
Validate: ownership, NOT isRunning (nemožno upraviť bežiaci)
Update: startTime, endTime, projectId, todoId, companyId, description
Prepočíta duration ak sa zmení čas
Set isEdited = true
deleteTimeEntry(entryId, userId)
Validate: ownership, NOT isRunning
DELETE FROM timeEntries
getMonthlyStats(userId, year, month)
Volá getMonthlyTimeEntries()
Počíta štatistiky:
- totalMinutes / totalHours
- daysWorked (unique days)
- averagePerDay
- byProject (čas per projekt)
- byCompany (čas per firma)
```
**Volá:**
- Databázu (timeEntries, projects, todos, companies)
- `utils/errors.NotFoundError`
- `utils/errors.BadRequestError`
**Dôležité poznámky:**
- **Auto-stop:** Pri štarte nového časovača sa automaticky zastaví predchádzajúci
- **Duration:** Ukladá sa v minútach (Math.round)
- **isEdited flag:** Označuje manuálne upravené záznamy
- **isRunning:** Iba jeden časovač môže byť aktívny pre usera
---
### 12. audit.service.js
**Účel:** Audit logging pre compliance
**Databáza:** `auditLogs`
@@ -1375,6 +1454,114 @@ Volá: jmap.service.sendEmail()
---
### ⏱️ TIME TRACKING
#### POST /api/time-tracking/start
```
Účel: Spustiť nový časovač
Body: { projectId?, todoId?, companyId?, description? }
Auth: Áno
Response: { entry with isRunning: true }
Efekt:
- Automaticky zastaví predchádzajúci bežiaci časovač (ak existuje)
- Vytvorí nový time entry s startTime = NOW()
```
#### POST /api/time-tracking/:entryId/stop
```
Účel: Zastaviť bežiaci časovač
Params: entryId (UUID)
Body: { projectId?, todoId?, companyId?, description? }
Auth: Áno
Response: { entry with endTime, duration, isRunning: false }
Efekt:
- Nastaví endTime = NOW()
- Vypočíta duration v minútach
- Optional: update projektId/todoId/companyId/description
```
#### GET /api/time-tracking/running
```
Účel: Získať aktuálny bežiaci časovač
Auth: Áno
Response: { entry } alebo null
```
#### GET /api/time-tracking?projectId=&todoId=&companyId=&startDate=&endDate=
```
Účel: Zoznam všetkých time entries s filtrami
Query: projectId, todoId, companyId, startDate, endDate (all optional)
Auth: Áno
Response: Array of time entries
Order: DESC by startTime
```
#### GET /api/time-tracking/month/:year/:month
```
Účel: Mesačný prehľad time entries
Params: year (YYYY), month (1-12)
Auth: Áno
Response: Array of entries pre daný mesiac
```
#### GET /api/time-tracking/stats/monthly/:year/:month
```
Účel: Mesačné štatistiky
Params: year (YYYY), month (1-12)
Auth: Áno
Response: {
totalMinutes,
totalHours,
remainingMinutes,
daysWorked,
averagePerDay,
entriesCount,
byProject: { projectId: minutes },
byCompany: { companyId: minutes }
}
```
#### GET /api/time-tracking/:entryId
```
Účel: Detail time entry
Params: entryId (UUID)
Auth: Áno
Response: Single time entry
```
#### GET /api/time-tracking/:entryId/details
```
Účel: Detail time entry s reláciami
Params: entryId (UUID)
Auth: Áno
Response: { ...entry, project, todo, company }
```
#### PATCH /api/time-tracking/:entryId
```
Účel: Upraviť time entry
Params: entryId (UUID)
Body: { startTime?, endTime?, projectId?, todoId?, companyId?, description? }
Auth: Áno
Validation:
- Entry nesmie byť isRunning (nemožno upraviť bežiaci časovač)
- User musí byť owner
- Pri zmene času sa prepočíta duration
Response: Updated entry with isEdited: true
```
#### DELETE /api/time-tracking/:entryId
```
Účel: Zmazať time entry
Params: entryId (UUID)
Auth: Áno
Validation:
- Entry nesmie byť isRunning
- User musí byť owner
```
---
### 📊 TIMESHEETS
#### POST /api/timesheets/upload
@@ -1455,6 +1642,18 @@ email.controller
→ jmapRequest() → JMAP Server
→ INSERT email to DB (sent)
TIME TRACKING:
time-tracking.controller
→ time-tracking.service.startTimeEntry()
→ Check running entry (auto-stop if exists)
→ Validate project/todo/company
→ INSERT timeEntries
→ time-tracking.service.stopTimeEntry()
→ Calculate duration
→ UPDATE timeEntries (set endTime, isRunning = false)
→ time-tracking.service.getMonthlyStats()
→ Aggregate statistics by project/company
AUDIT:
Rôzne controllers
→ audit.service.logAuditEvent()
@@ -1478,6 +1677,7 @@ Rôzne controllers
- `company.service` → database, errors
- `todo.service` → database, errors
- `note.service` → database, errors
- `time-tracking.service` → database, errors
**Tier 4 (Multiple services):**
- `contact.service` → company.service, jmap.service
@@ -1509,12 +1709,14 @@ Rôzne controllers
### Možné zlepšenia
1. Pagination na všetkých list endpointoch
2. WebSocket pre real-time notifications
2. WebSocket pre real-time notifications (time tracking updates, email sync)
3. Background jobs pre email sync (Bull/Redis)
4. Cache layer (Redis) pre často čítané dáta
5. API versioning (/api/v1/)
6. GraphQL ako alternatíva k REST
7. Implement standalone Notes UI alebo vymazať routes
8. Time tracking export do CSV/Excel pre reporting
9. Team time tracking dashboard (admin view všetkých userov)
---
@@ -1697,6 +1899,51 @@ req.userRole // 'admin' | 'member'
---
### 7. Time Tracking - Auto-stop Behavior
**PROBLÉM:** User môže mať spustený iba jeden časovač naraz.
**RIEŠENIE - Automatický stop:**
```javascript
// Pri štarte nového časovača
if (existingRunning) {
// Automaticky zastaví predchádzajúci časovač
const endTime = new Date();
const duration = Math.round((endTime - startTime) / 60000); // minúty
await db.update(timeEntries)
.set({ endTime, duration, isRunning: false })
.where(eq(timeEntries.id, existingRunning.id));
}
```
**Validačné pravidlá:**
```javascript
// ✅ POVOLENÉ
- Spustiť nový časovač (auto-stop predchádzajúceho)
- Upraviť zastavený časovač
- Zmazať zastavený časovač
// ❌ ZAKÁZANÉ
- Upraviť bežiaci časovač (musí sa najprv zastaviť)
- Zmazať bežiaci časovač (musí sa najprv zastaviť)
- Zastaviť časovač iného usera
```
**Duration calculation:**
- Ukladá sa v **minútach** (Math.round)
- Počíta sa z rozdelu: `(endTime - startTime) / 60000`
- Frontend zobrazuje: hodiny + minúty (napr. "2h 35m")
**isEdited flag:**
- Automaticky nastavený pri manuálnej úprave
- Indikuje, že čas bol zmenený používateľom (nie auto-tracked)
**Kde sa používa:**
- `time-tracking.service.js`: `startTimeEntry()`, `updateTimeEntry()`
- Frontend: Time tracking komponenty s real-time countdown
---
## 🔍 DEBUGGING TIPS
### 1. Server beží starý kód
@@ -1732,6 +1979,6 @@ console.log('[DEBUG] JMAP validation:', valid);
---
**Vytvorené:** 2025-11-21
**Posledná aktualizácia:** 2025-11-21
**Posledná aktualizácia:** 2025-11-24
**Autor:** CRM Server Team
**Kontakt:** crm-server documentation