Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 49 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,17 +107,19 @@ Alle Frontends verwenden denselben Login-Endpunkt über den API-Gateway (`localh
| Seite | URL | Funktion |
|---|---|---|
| Benutzerverwaltung | `/users` | Schichtleiter/Mitarbeiter anlegen, bearbeiten, deaktivieren |
| Stundenübersicht | `/time` | Gesamtstunden pro Zeitraum, Monatsdetail pro Mitarbeiter |
| Stundenübersicht | `/time` | Gesamtstunden, Monatsdetail und Pausenverstösse prüfen |
| Stundenfreigabe | `/hour-budgets` | Monatliche HR-Stundenkontingente für Schichtleiter festlegen |
| Rechnungen | `/invoices` | Rechnungen erstellen, versenden, als bezahlt markieren |
| Lohnauszüge | `/payroll` | Monatslohn aus Stunden, Rate, Zuschlägen und Abzügen berechnen |
| Absenzen & Ferien | `/absences` | Ferienanträge genehmigen/ablehnen, Absenzen erfassen |

**Schichtleiter** → http://localhost:3003

| Seite | URL | Funktion |
|---|---|---|
| Arbeitsplanung | `/planning` | Arbeitspläne erstellen, Schichten hinzufügen, veröffentlichen |
| Arbeitsplanung | `/planning` | Arbeitspläne mit HR-Stundenfreigabe erstellen, Schichten hinzufügen, veröffentlichen |
| Aufträge | `/orders` | Zugewiesene Aufträge aus dem Order Service einsehen und Status ändern |
| Arbeitszeiten | `/time` | Gesamtstunden und Monatsdetails der Mitarbeiter aus dem Time Service einsehen |
| Arbeitszeiten | `/time` | Gesamtstunden, Monatsdetails und Pausenverstösse der Mitarbeiter einsehen |

**Flutter Mobile App** (Login: `emp.meier` / `password`)

Expand Down Expand Up @@ -498,25 +500,44 @@ Content-Type: application/json

### 6.5 Planning Service · Port 8004

**Aufgabe:** Schichtleiter erstellt Arbeitspläne für sein Team. Mitarbeiter sehen ihren Arbeitskalender. Der Service verwaltet HR-Stundenkontingente, geplante Schichten, Warnungen bei Über-/Unterplanung und den veröffentlichten Kalender für die Mobile App.
**Aufgabe:** HR gibt monatliche Stundenkontingente pro Schichtleiter frei. Schichtleiter erstellen darauf basierend Arbeitspläne für ihr Team. Mitarbeiter sehen veröffentlichte Schichten im Mobile-Kalender.

**Implementiert:**
- `POST /api/planning/workplans` – Arbeitsplan mit HR-Stundenkontingent erstellen
- `POST /api/planning/hour-budgets` – HR-Stundenkontingent pro Schichtleiter und Monat erstellen/aktualisieren
- `GET /api/planning/hour-budgets` – HR-Stundenkontingente auflisten, optional mit `?shiftLeadId=` filtern
- `POST /api/planning/workplans` – Arbeitsplan erstellen und HR-Stundenkontingent automatisch übernehmen
- `GET /api/planning/workplans` – Arbeitspläne auflisten, optional mit `?shiftLeadId=` filtern
- `GET /api/planning/workplans/{id}` – Arbeitsplan inkl. Schichten und Stundenübersicht anzeigen
- `PUT /api/planning/workplans/{id}` – Arbeitsplan-Entwurf bearbeiten
- `POST /api/planning/workplans/{id}/shifts` – Schicht hinzufügen, optional mit `orderId`
- `PUT /api/planning/workplans/{id}/publish` – Arbeitsplan veröffentlichen
- `GET /api/planning/calendar/{employeeId}` – veröffentlichte Kalenderschichten eines Mitarbeiters anzeigen
- Entities: `WorkPlan`, `Shift`, `WorkPlanStatus`
- Entities: `HourBudget`, `WorkPlan`, `Shift`, `WorkPlanStatus`

**Stundenlogik:**
- `approvedHours` enthält die von HR freigegebenen Monats-/Planstunden.
- `approvedHours` wird aus der HR-Stundenfreigabe übernommen und nicht mehr vom Schichtleiter eingegeben.
- `plannedHours` wird aus allen Schichten eines Arbeitsplans berechnet.
- `remainingHours` zeigt die Differenz zwischen freigegebenen und geplanten Stunden.
- `overLimit` wird `true`, wenn mehr als das HR-Kontingent geplant wurde.
- `underPlanned` wird `true`, wenn weniger als 95 % des HR-Kontingents geplant wurden.

**Beispiel: HR-Stundenfreigabe erstellen**

```http
POST http://localhost:8000/api/planning/hour-budgets
Authorization: Bearer <hr-token>
Content-Type: application/json

{
"shiftLeadId": 3,
"year": 2026,
"month": 6,
"approvedHours": 1000,
"createdBy": 2,
"notes": "Sommermonat Juni"
}
```

**Beispiel: Arbeitsplan erstellen**

```http
Expand All @@ -528,8 +549,7 @@ Content-Type: application/json
"title": "Monatsplan Juni",
"shiftLeadId": 3,
"startDate": "2026-06-01",
"endDate": "2026-06-30",
"approvedHours": 1000
"endDate": "2026-06-30"
}
```

Expand Down Expand Up @@ -565,9 +585,11 @@ Content-Type: application/json
- `GET /api/time/month/{employeeId}?month=&year=` – Monatsauswertung pro Mitarbeiter
- `GET /api/time/total?from=&to=` – Gesamtstunden aller Mitarbeiter im Zeitraum
- `GET /api/time/total/{employeeId}?from=&to=` – Gesamtstunden eines Mitarbeiters im Zeitraum
- `GET /api/time/break-violations?from=&to=&employeeId=` – Pausenverstösse auswerten
- Rollen: HR/Admin für Auswertungen, Schichtleiter für Team-Übersicht, Mitarbeiter für eigenen Check-in/out
- Entities: `TimeEntry`
- Berechnung: Netto-Stunden aus Check-in, Check-out und Pausenzeit
- Pausenregel: mehr als 6 Stunden Brutto-Arbeitszeit → mindestens 30 Minuten Pause; mehr als 9 Stunden → mindestens 45 Minuten Pause

---

Expand All @@ -592,16 +614,26 @@ Content-Type: application/json

### 6.8 Billing Service · Port 8007

**Aufgabe:** HR erstellt Rechnungen basierend auf erfassten Arbeitsstunden.
**Aufgabe:** HR erstellt Rechnungen und monatliche Lohnauszüge basierend auf erfassten Arbeitsstunden.

**Noch zu implementieren:**
**Implementiert Rechnungen:**
- `POST /api/billing/invoices` – Rechnung erstellen
- `GET /api/billing/invoices` – alle Rechnungen
- `GET /api/billing/invoices/{id}` – Rechnungsdetail
- `PUT /api/billing/invoices/{id}/send` – Rechnung versenden
- `PUT /api/billing/invoices/{id}/pay` – Rechnung als bezahlt markieren
- Entities: `Invoice`, `InvoicePosition`
- Status-Enum: `DRAFT`, `SENT`, `PAID`
- Stundendaten kommen vom Time Service

**Implementiert Lohnauszüge:**
- `POST /api/billing/payroll-statements` – Lohnauszug aus Monatsstunden, Stundenrate, Zuschlägen und Abzügen erstellen oder neu berechnen
- `GET /api/billing/payroll-statements` – Lohnauszüge auflisten, optional mit `?status=` filtern
- `GET /api/billing/payroll-statements/{id}` – Lohnauszug anzeigen
- `PUT /api/billing/payroll-statements/{id}/approve` – Lohnauszug freigeben
- `PUT /api/billing/payroll-statements/{id}/pay` – Lohnauszug als bezahlt markieren
- Entities: `PayrollStatement`, `PayrollStatus`
- Status-Enum: `DRAFT`, `APPROVED`, `PAID`
- Stundendaten kommen aus `time_entries` des Time Service

---

Expand Down Expand Up @@ -669,8 +701,10 @@ note=Rapportfoto Eingang A
**Implementiert:**
- Login mit Rollenprüfung `HR`
- Benutzerverwaltung (`/users`): Schichtleiter/Mitarbeiter anlegen, bearbeiten, deaktivieren
- Stundenübersicht (`/time`): Gesamtstunden aller Mitarbeiter, Monatsdetail pro Mitarbeiter
- Stundenübersicht (`/time`): Gesamtstunden, Monatsdetail und Pausenverstösse prüfen
- Stundenfreigabe (`/hour-budgets`): Monatskontingente pro Schichtleiter freigeben
- Rechnungen (`/invoices`): Erstellen, versenden, als bezahlt markieren (DRAFT → SENT → PAID)
- Lohnauszüge (`/payroll`): Monatslohn aus Stunden, Stundenrate, Zuschlägen und Abzügen berechnen (DRAFT → APPROVED → PAID)
- Absenzen & Ferien (`/absences`): Ferienanfragen genehmigen/ablehnen, Absenzen erfassen und verwalten

**Noch zu implementieren:**
Expand All @@ -681,10 +715,10 @@ note=Rapportfoto Eingang A
**Implementiert:**
- Login mit Rollenprüfung `SHIFT_LEAD` und Speicherung von `userId`
- Dashboard mit Kacheln für Planung, Aufträge und Notizen
- Arbeitsplan-Erstellung (`/planning`)
- Arbeitsplan-Erstellung (`/planning`) mit automatisch übernommener HR-Stundenfreigabe
- Schichten hinzufügen inklusive Mitarbeiter-Auswahl, optionaler Auftrag-ID und Notiz
- Stundenübersicht mit HR-Kontingent, geplanten Stunden, Reststunden und Warnungen
- Arbeitszeiten-Seite (`/time`) lädt Gesamtstunden und Monatsdetails aus dem Time Service
- Arbeitszeiten-Seite (`/time`) lädt Gesamtstunden, Monatsdetails und Pausenverstösse aus dem Time Service
- Arbeitsplan veröffentlichen, damit Mitarbeiter die Schichten im Mobile-Kalender sehen

**Implementiert zusätzlich:**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.workforce.billing.controller;

import com.workforce.billing.dto.CreateInvoiceRequest;
import com.workforce.billing.dto.CreatePayrollStatementRequest;
import com.workforce.billing.dto.InvoiceResponse;
import com.workforce.billing.dto.PayrollStatementResponse;
import com.workforce.billing.service.BillingService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
Expand Down Expand Up @@ -94,4 +96,41 @@ public ResponseEntity<InvoiceResponse> send(@PathVariable Long id) {
public ResponseEntity<InvoiceResponse> pay(@PathVariable Long id) {
return ResponseEntity.ok(billingService.markPaid(id));
}
/** Gibt alle Lohnauszüge zurück, optional gefiltert nach Status. */
@GetMapping("/payroll-statements")
@PreAuthorize("hasAnyRole('HR', 'ADMIN')")
public ResponseEntity<List<PayrollStatementResponse>> getPayrollStatements(
@RequestParam(required = false) String status) {
return ResponseEntity.ok(billingService.getPayrollStatements(status));
}

/** Gibt einen einzelnen Lohnauszug zurück. */
@GetMapping("/payroll-statements/{id}")
@PreAuthorize("hasAnyRole('HR', 'ADMIN')")
public ResponseEntity<PayrollStatementResponse> getPayrollStatement(@PathVariable Long id) {
return ResponseEntity.ok(billingService.getPayrollStatement(id));
}

/** Erstellt oder berechnet einen monatlichen Lohnauszug aus den erfassten Arbeitsstunden. */
@PostMapping("/payroll-statements")
@PreAuthorize("hasAnyRole('HR', 'ADMIN')")
public ResponseEntity<PayrollStatementResponse> createPayrollStatement(
@RequestBody CreatePayrollStatementRequest request) {
return ResponseEntity.status(HttpStatus.CREATED).body(billingService.createPayrollStatement(request));
}

/** Gibt einen Lohnauszug frei. */
@PutMapping("/payroll-statements/{id}/approve")
@PreAuthorize("hasAnyRole('HR', 'ADMIN')")
public ResponseEntity<PayrollStatementResponse> approvePayrollStatement(@PathVariable Long id) {
return ResponseEntity.ok(billingService.approvePayrollStatement(id));
}

/** Markiert einen Lohnauszug als bezahlt. */
@PutMapping("/payroll-statements/{id}/pay")
@PreAuthorize("hasAnyRole('HR', 'ADMIN')")
public ResponseEntity<PayrollStatementResponse> payPayrollStatement(@PathVariable Long id) {
return ResponseEntity.ok(billingService.payPayrollStatement(id));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.workforce.billing.dto;

import java.math.BigDecimal;

/** DTO zum Erstellen oder Neuberechnen eines monatlichen Lohnauszugs. */
public record CreatePayrollStatementRequest(
Long employeeId,
Integer year,
Integer month,
BigDecimal hourlyRate,
BigDecimal bonusAmount,
BigDecimal deductionAmount,
Long createdBy
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.workforce.billing.dto;

import com.workforce.billing.model.PayrollStatement;
import com.workforce.billing.model.PayrollStatus;

import java.math.BigDecimal;
import java.time.LocalDateTime;

/** Rückgabe-DTO für monatliche Lohnauszüge. */
public record PayrollStatementResponse(
Long id,
Long employeeId,
Integer year,
Integer month,
BigDecimal hourlyRate,
BigDecimal totalHours,
BigDecimal grossAmount,
BigDecimal bonusAmount,
BigDecimal deductionAmount,
BigDecimal netAmount,
PayrollStatus status,
Long createdBy,
LocalDateTime createdAt,
LocalDateTime updatedAt
) {
public static PayrollStatementResponse from(PayrollStatement statement) {
return new PayrollStatementResponse(
statement.getId(),
statement.getEmployeeId(),
statement.getYear(),
statement.getMonth(),
statement.getHourlyRate(),
statement.getTotalHours(),
statement.getGrossAmount(),
statement.getBonusAmount(),
statement.getDeductionAmount(),
statement.getNetAmount(),
statement.getStatus(),
statement.getCreatedBy(),
statement.getCreatedAt(),
statement.getUpdatedAt()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.workforce.billing.model;

import jakarta.persistence.*;
import lombok.Data;

import java.math.BigDecimal;
import java.time.LocalDateTime;

/** JPA-Entity für monatliche Lohnauszüge von Mitarbeitern. */
@Data
@Entity
@Table(
name = "payroll_statements",
uniqueConstraints = @UniqueConstraint(
name = "uk_payroll_employee_month",
columnNames = {"employee_id", "payroll_year", "payroll_month"}
)
)
public class PayrollStatement {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "employee_id", nullable = false)
private Long employeeId;

@Column(name = "payroll_year", nullable = false)
private Integer year;

@Column(name = "payroll_month", nullable = false)
private Integer month;

@Column(name = "hourly_rate", nullable = false, precision = 8, scale = 2)
private BigDecimal hourlyRate = BigDecimal.ZERO;

@Column(name = "total_hours", nullable = false, precision = 8, scale = 2)
private BigDecimal totalHours = BigDecimal.ZERO;

@Column(name = "gross_amount", nullable = false, precision = 10, scale = 2)
private BigDecimal grossAmount = BigDecimal.ZERO;

@Column(name = "bonus_amount", nullable = false, precision = 10, scale = 2)
private BigDecimal bonusAmount = BigDecimal.ZERO;

@Column(name = "deduction_amount", nullable = false, precision = 10, scale = 2)
private BigDecimal deductionAmount = BigDecimal.ZERO;

@Column(name = "net_amount", nullable = false, precision = 10, scale = 2)
private BigDecimal netAmount = BigDecimal.ZERO;

@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 30)
private PayrollStatus status = PayrollStatus.DRAFT;

@Column(name = "created_by")
private Long createdBy;

@Column(name = "created_at", nullable = false)
private LocalDateTime createdAt;

@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;

@PrePersist
void prePersist() {
LocalDateTime now = LocalDateTime.now();
if (createdAt == null) createdAt = now;
if (updatedAt == null) updatedAt = now;
if (status == null) status = PayrollStatus.DRAFT;
}

@PreUpdate
void preUpdate() {
updatedAt = LocalDateTime.now();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.workforce.billing.model;

/** Status eines monatlichen Lohnauszugs. */
public enum PayrollStatus {
DRAFT,
APPROVED,
PAID
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.workforce.billing.repository;

import com.workforce.billing.model.PayrollStatement;
import com.workforce.billing.model.PayrollStatus;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;
import java.util.Optional;

/** Spring Data JPA Repository für Lohnauszüge. */
public interface PayrollStatementRepository extends JpaRepository<PayrollStatement, Long> {

Optional<PayrollStatement> findByEmployeeIdAndYearAndMonth(Long employeeId, Integer year, Integer month);

List<PayrollStatement> findByStatusOrderByYearDescMonthDescEmployeeIdAsc(PayrollStatus status);

List<PayrollStatement> findAllByOrderByYearDescMonthDescEmployeeIdAsc();
}
Loading
Loading