excel preview & file handling
This commit is contained in:
255
DOKUMENTACIA.md
255
DOKUMENTACIA.md
@@ -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 sú 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
|
||||
|
||||
Reference in New Issue
Block a user