Skip to content

Commit c8cb62d

Browse files
Merge pull request #34 from Stcwal/backend/ik-alcohol-clean
Add alcohol compliance (IK-Alkohol) module
2 parents c671b84 + 3aefbd2 commit c8cb62d

31 files changed

Lines changed: 2041 additions & 4 deletions
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package backend.fullstack.alcohol.api;
2+
3+
import java.time.LocalDateTime;
4+
import java.util.List;
5+
6+
import org.springframework.format.annotation.DateTimeFormat;
7+
import org.springframework.http.ResponseEntity;
8+
import org.springframework.security.access.prepost.PreAuthorize;
9+
import org.springframework.web.bind.annotation.GetMapping;
10+
import org.springframework.web.bind.annotation.PathVariable;
11+
import org.springframework.web.bind.annotation.PostMapping;
12+
import org.springframework.web.bind.annotation.RequestBody;
13+
import org.springframework.web.bind.annotation.RequestMapping;
14+
import org.springframework.web.bind.annotation.RequestParam;
15+
import org.springframework.web.bind.annotation.RestController;
16+
17+
import backend.fullstack.alcohol.api.dto.AgeVerificationRequest;
18+
import backend.fullstack.alcohol.api.dto.AgeVerificationResponse;
19+
import backend.fullstack.alcohol.application.AgeVerificationService;
20+
import backend.fullstack.config.ApiResponse;
21+
import io.swagger.v3.oas.annotations.Operation;
22+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
23+
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
24+
import io.swagger.v3.oas.annotations.tags.Tag;
25+
import jakarta.validation.Valid;
26+
27+
/**
28+
* REST endpoints for age verification logging under IK-Alkohol.
29+
*/
30+
@RestController
31+
@RequestMapping("/api/alcohol/age-verifications")
32+
@Tag(name = "Age Verifications", description = "Log and query age verification checks for alcohol service")
33+
@SecurityRequirement(name = "Bearer Auth")
34+
public class AgeVerificationController {
35+
36+
private final AgeVerificationService verificationService;
37+
38+
public AgeVerificationController(AgeVerificationService verificationService) {
39+
this.verificationService = verificationService;
40+
}
41+
42+
@PostMapping
43+
@PreAuthorize("hasAnyRole('ADMIN','MANAGER','STAFF','SUPERVISOR')")
44+
@Operation(summary = "Log a new age verification check")
45+
@ApiResponses(value = {
46+
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "Verification logged"),
47+
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "Forbidden")
48+
})
49+
public ResponseEntity<ApiResponse<AgeVerificationResponse>> create(
50+
@Valid @RequestBody AgeVerificationRequest request
51+
) {
52+
return ResponseEntity.status(201)
53+
.body(ApiResponse.success("Age verification logged", verificationService.create(request)));
54+
}
55+
56+
@GetMapping
57+
@PreAuthorize("hasAnyRole('ADMIN','MANAGER','SUPERVISOR')")
58+
@Operation(summary = "List age verification logs with optional filters")
59+
public ApiResponse<List<AgeVerificationResponse>> list(
60+
@RequestParam(required = false) Long locationId,
61+
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime from,
62+
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime to
63+
) {
64+
return ApiResponse.success("Verifications fetched", verificationService.list(locationId, from, to));
65+
}
66+
67+
@GetMapping("/{id}")
68+
@PreAuthorize("hasAnyRole('ADMIN','MANAGER','SUPERVISOR')")
69+
@Operation(summary = "Get age verification log by id")
70+
public ApiResponse<AgeVerificationResponse> getById(@PathVariable Long id) {
71+
return ApiResponse.success("Verification fetched", verificationService.getById(id));
72+
}
73+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package backend.fullstack.alcohol.api;
2+
3+
import java.time.LocalDateTime;
4+
import java.util.List;
5+
6+
import org.springframework.format.annotation.DateTimeFormat;
7+
import org.springframework.http.ResponseEntity;
8+
import org.springframework.security.access.prepost.PreAuthorize;
9+
import org.springframework.web.bind.annotation.GetMapping;
10+
import org.springframework.web.bind.annotation.PatchMapping;
11+
import org.springframework.web.bind.annotation.PathVariable;
12+
import org.springframework.web.bind.annotation.PostMapping;
13+
import org.springframework.web.bind.annotation.RequestBody;
14+
import org.springframework.web.bind.annotation.RequestMapping;
15+
import org.springframework.web.bind.annotation.RequestParam;
16+
import org.springframework.web.bind.annotation.RestController;
17+
18+
import backend.fullstack.alcohol.api.dto.AlcoholIncidentRequest;
19+
import backend.fullstack.alcohol.api.dto.AlcoholIncidentResponse;
20+
import backend.fullstack.alcohol.api.dto.ResolveIncidentRequest;
21+
import backend.fullstack.alcohol.application.AlcoholIncidentService;
22+
import backend.fullstack.alcohol.domain.IncidentStatus;
23+
import backend.fullstack.alcohol.domain.IncidentType;
24+
import backend.fullstack.config.ApiResponse;
25+
import io.swagger.v3.oas.annotations.Operation;
26+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
27+
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
28+
import io.swagger.v3.oas.annotations.tags.Tag;
29+
import jakarta.validation.Valid;
30+
31+
/**
32+
* REST endpoints for managing alcohol-related serving incidents.
33+
*/
34+
@RestController
35+
@RequestMapping("/api/alcohol/incidents")
36+
@Tag(name = "Alcohol Incidents", description = "Report and manage alcohol serving incidents")
37+
@SecurityRequirement(name = "Bearer Auth")
38+
public class AlcoholIncidentController {
39+
40+
private final AlcoholIncidentService incidentService;
41+
42+
public AlcoholIncidentController(AlcoholIncidentService incidentService) {
43+
this.incidentService = incidentService;
44+
}
45+
46+
@PostMapping
47+
@PreAuthorize("hasAnyRole('ADMIN','MANAGER','STAFF','SUPERVISOR')")
48+
@Operation(summary = "Report a new alcohol serving incident")
49+
@ApiResponses(value = {
50+
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "Incident reported"),
51+
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "Forbidden")
52+
})
53+
public ResponseEntity<ApiResponse<AlcoholIncidentResponse>> create(
54+
@Valid @RequestBody AlcoholIncidentRequest request
55+
) {
56+
return ResponseEntity.status(201)
57+
.body(ApiResponse.success("Incident reported", incidentService.create(request)));
58+
}
59+
60+
@GetMapping
61+
@PreAuthorize("hasAnyRole('ADMIN','MANAGER','SUPERVISOR')")
62+
@Operation(summary = "List alcohol incidents with optional filters")
63+
public ApiResponse<List<AlcoholIncidentResponse>> list(
64+
@RequestParam(required = false) Long locationId,
65+
@RequestParam(required = false) IncidentStatus status,
66+
@RequestParam(required = false) IncidentType incidentType,
67+
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime from,
68+
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime to
69+
) {
70+
return ApiResponse.success("Incidents fetched", incidentService.list(locationId, status, incidentType, from, to));
71+
}
72+
73+
@GetMapping("/{id}")
74+
@PreAuthorize("hasAnyRole('ADMIN','MANAGER','SUPERVISOR')")
75+
@Operation(summary = "Get alcohol incident by id")
76+
public ApiResponse<AlcoholIncidentResponse> getById(@PathVariable Long id) {
77+
return ApiResponse.success("Incident fetched", incidentService.getById(id));
78+
}
79+
80+
@PatchMapping("/{id}/resolve")
81+
@PreAuthorize("hasAnyRole('ADMIN','MANAGER','SUPERVISOR')")
82+
@Operation(summary = "Resolve an alcohol incident with corrective action")
83+
public ApiResponse<AlcoholIncidentResponse> resolve(
84+
@PathVariable Long id,
85+
@Valid @RequestBody ResolveIncidentRequest request
86+
) {
87+
return ApiResponse.success("Incident resolved", incidentService.resolve(id, request));
88+
}
89+
90+
@PatchMapping("/{id}/status")
91+
@PreAuthorize("hasAnyRole('ADMIN','SUPERVISOR')")
92+
@Operation(summary = "Update incident status (e.g. close an incident)")
93+
public ApiResponse<AlcoholIncidentResponse> updateStatus(
94+
@PathVariable Long id,
95+
@RequestParam IncidentStatus status
96+
) {
97+
return ApiResponse.success("Incident status updated", incidentService.updateStatus(id, status));
98+
}
99+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package backend.fullstack.alcohol.api;
2+
3+
import java.util.List;
4+
5+
import org.springframework.http.ResponseEntity;
6+
import org.springframework.security.access.prepost.PreAuthorize;
7+
import org.springframework.web.bind.annotation.DeleteMapping;
8+
import org.springframework.web.bind.annotation.GetMapping;
9+
import org.springframework.web.bind.annotation.PathVariable;
10+
import org.springframework.web.bind.annotation.PostMapping;
11+
import org.springframework.web.bind.annotation.PutMapping;
12+
import org.springframework.web.bind.annotation.RequestBody;
13+
import org.springframework.web.bind.annotation.RequestMapping;
14+
import org.springframework.web.bind.annotation.RestController;
15+
16+
import backend.fullstack.alcohol.api.dto.AlcoholLicenseRequest;
17+
import backend.fullstack.alcohol.api.dto.AlcoholLicenseResponse;
18+
import backend.fullstack.alcohol.application.AlcoholLicenseService;
19+
import backend.fullstack.config.ApiResponse;
20+
import io.swagger.v3.oas.annotations.Operation;
21+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
22+
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
23+
import io.swagger.v3.oas.annotations.tags.Tag;
24+
import jakarta.validation.Valid;
25+
26+
/**
27+
* REST endpoints for managing alcohol licenses (bevillinger).
28+
*/
29+
@RestController
30+
@RequestMapping("/api/alcohol/licenses")
31+
@Tag(name = "Alcohol Licenses", description = "Manage organization alcohol licenses (bevillinger)")
32+
@SecurityRequirement(name = "Bearer Auth")
33+
public class AlcoholLicenseController {
34+
35+
private final AlcoholLicenseService licenseService;
36+
37+
public AlcoholLicenseController(AlcoholLicenseService licenseService) {
38+
this.licenseService = licenseService;
39+
}
40+
41+
@GetMapping
42+
@PreAuthorize("hasAnyRole('ADMIN','MANAGER','SUPERVISOR')")
43+
@Operation(summary = "List all alcohol licenses for the organization")
44+
public ApiResponse<List<AlcoholLicenseResponse>> list() {
45+
return ApiResponse.success("Licenses fetched", licenseService.listLicenses());
46+
}
47+
48+
@GetMapping("/{id}")
49+
@PreAuthorize("hasAnyRole('ADMIN','MANAGER','SUPERVISOR')")
50+
@Operation(summary = "Get alcohol license by id")
51+
public ApiResponse<AlcoholLicenseResponse> getById(@PathVariable Long id) {
52+
return ApiResponse.success("License fetched", licenseService.getById(id));
53+
}
54+
55+
@PostMapping
56+
@PreAuthorize("hasRole('ADMIN')")
57+
@Operation(summary = "Register a new alcohol license")
58+
@ApiResponses(value = {
59+
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "License created"),
60+
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "Forbidden")
61+
})
62+
public ResponseEntity<ApiResponse<AlcoholLicenseResponse>> create(
63+
@Valid @RequestBody AlcoholLicenseRequest request
64+
) {
65+
return ResponseEntity.status(201)
66+
.body(ApiResponse.success("License created", licenseService.create(request)));
67+
}
68+
69+
@PutMapping("/{id}")
70+
@PreAuthorize("hasRole('ADMIN')")
71+
@Operation(summary = "Update an alcohol license")
72+
public ApiResponse<AlcoholLicenseResponse> update(
73+
@PathVariable Long id,
74+
@Valid @RequestBody AlcoholLicenseRequest request
75+
) {
76+
return ApiResponse.success("License updated", licenseService.update(id, request));
77+
}
78+
79+
@DeleteMapping("/{id}")
80+
@PreAuthorize("hasRole('ADMIN')")
81+
@Operation(summary = "Delete an alcohol license")
82+
public ApiResponse<Void> delete(@PathVariable Long id) {
83+
licenseService.delete(id);
84+
return ApiResponse.success("License deleted", null);
85+
}
86+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package backend.fullstack.alcohol.api.dto;
2+
3+
import java.time.LocalDateTime;
4+
5+
import backend.fullstack.alcohol.domain.VerificationMethod;
6+
import io.swagger.v3.oas.annotations.media.Schema;
7+
import jakarta.validation.constraints.NotNull;
8+
import lombok.Getter;
9+
import lombok.Setter;
10+
11+
@Getter
12+
@Setter
13+
@Schema(description = "Request to log an age verification check")
14+
public class AgeVerificationRequest {
15+
16+
@NotNull(message = "Location id is required")
17+
@Schema(description = "Location where verification was performed", example = "1")
18+
private Long locationId;
19+
20+
@NotNull(message = "Verification method is required")
21+
@Schema(description = "How the age was verified", example = "ID_CHECKED")
22+
private VerificationMethod verificationMethod;
23+
24+
@Schema(description = "Whether the guest appeared to be underage", example = "true")
25+
private boolean guestAppearedUnderage = true;
26+
27+
@Schema(description = "Whether the ID was valid (null if no ID checked)")
28+
private Boolean idWasValid;
29+
30+
@Schema(description = "Whether service was refused", example = "false")
31+
private boolean wasRefused = false;
32+
33+
@Schema(description = "Additional notes about the verification")
34+
private String note;
35+
36+
@Schema(description = "When the verification occurred (defaults to now)")
37+
private LocalDateTime verifiedAt;
38+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package backend.fullstack.alcohol.api.dto;
2+
3+
import java.time.LocalDateTime;
4+
5+
import backend.fullstack.alcohol.domain.VerificationMethod;
6+
import io.swagger.v3.oas.annotations.media.Schema;
7+
import lombok.AllArgsConstructor;
8+
import lombok.Builder;
9+
import lombok.Getter;
10+
import lombok.NoArgsConstructor;
11+
import lombok.Setter;
12+
13+
@Getter
14+
@Setter
15+
@Builder
16+
@NoArgsConstructor
17+
@AllArgsConstructor
18+
@Schema(description = "Age verification log response")
19+
public class AgeVerificationResponse {
20+
21+
@Schema(example = "1")
22+
private Long id;
23+
24+
@Schema(example = "100")
25+
private Long organizationId;
26+
27+
@Schema(example = "1")
28+
private Long locationId;
29+
30+
@Schema(example = "Bar")
31+
private String locationName;
32+
33+
@Schema(example = "5")
34+
private Long verifiedByUserId;
35+
36+
@Schema(example = "Ola Nordmann")
37+
private String verifiedByName;
38+
39+
@Schema(example = "ID_CHECKED")
40+
private VerificationMethod verificationMethod;
41+
42+
private boolean guestAppearedUnderage;
43+
44+
private Boolean idWasValid;
45+
46+
private boolean wasRefused;
47+
48+
private String note;
49+
50+
private LocalDateTime verifiedAt;
51+
52+
private LocalDateTime createdAt;
53+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package backend.fullstack.alcohol.api.dto;
2+
3+
import java.time.LocalDateTime;
4+
5+
import backend.fullstack.alcohol.domain.IncidentSeverity;
6+
import backend.fullstack.alcohol.domain.IncidentType;
7+
import io.swagger.v3.oas.annotations.media.Schema;
8+
import jakarta.validation.constraints.NotBlank;
9+
import jakarta.validation.constraints.NotNull;
10+
import lombok.Getter;
11+
import lombok.Setter;
12+
13+
@Getter
14+
@Setter
15+
@Schema(description = "Request to report an alcohol serving incident")
16+
public class AlcoholIncidentRequest {
17+
18+
@NotNull(message = "Location id is required")
19+
@Schema(description = "Location where the incident occurred", example = "1")
20+
private Long locationId;
21+
22+
@NotNull(message = "Incident type is required")
23+
@Schema(description = "Type of incident", example = "REFUSED_SERVICE")
24+
private IncidentType incidentType;
25+
26+
@NotNull(message = "Severity is required")
27+
@Schema(description = "Severity of the incident", example = "MEDIUM")
28+
private IncidentSeverity severity;
29+
30+
@NotBlank(message = "Description is required")
31+
@Schema(description = "Description of what happened", example = "Guest appeared intoxicated and was refused further service")
32+
private String description;
33+
34+
@Schema(description = "When the incident occurred (defaults to now)")
35+
private LocalDateTime occurredAt;
36+
}

0 commit comments

Comments
 (0)