-
-
+
+
Part number
+
+ {{ partNumber }}
+
+
+
+
+
+
+
Description
+
+ {{ description }}
+
+
+
+
+
@@ -88,6 +87,10 @@ export default {
openSelectDirect: {
type: Boolean,
default: false,
+ },
+ responsive: {
+ type: Boolean,
+ default: true,
}
},
computed: {
@@ -164,16 +167,63 @@ export default {
display: flex;
align-items: center;
gap: 0.8rem;
+ width: 100%;
}
.container {
display: grid;
grid-template-columns: auto 1fr;
- grid-template-rows: repeat(3, fit-content(0));
+ grid-template-rows: repeat(4, fit-content(0));
gap: 1.6rem;
flex: 1 1 auto;
}
+/* Responsive Layout für Breiten unter 1500px - nur wenn responsive aktiviert ist */
+@media (max-width: 1500px) {
+ .container.responsive {
+ grid-template-columns:
+ minmax(auto, max-content) /* Spalte 1: Label */
+ minmax(120px, 1fr) /* Spalte 2: Input */
+ minmax(auto, max-content) /* Spalte 3: Label */
+ minmax(120px, 1fr) /* Spalte 4: Input/Dropdown */
+ minmax(auto, max-content) /* Spalte 5: Label */
+ minmax(120px, 1fr) /* Spalte 6: Input */
+ minmax(auto, max-content) /* Spalte 7: Label */
+ minmax(120px, 1fr); /* Spalte 8: Input/Dropdown */
+ grid-template-rows: auto;
+ gap: 1.2rem 1rem;
+ align-items: center;
+ }
+
+ .container.responsive .field-group {
+ display: contents;
+ }
+
+ .container.responsive .caption-column {
+ justify-self: start;
+ }
+
+ .container.responsive .input-column {
+ min-width: 0;
+ }
+
+ .container.responsive .field-group {
+ display: contents;
+ }
+
+ .container.responsive .caption-column {
+ justify-self: start;
+ }
+
+ .container.responsive .input-column {
+ min-width: 0;
+ }
+}
+
+.field-group {
+ display: contents;
+}
+
.input-column {
display: flex;
justify-content: space-between;
@@ -201,7 +251,6 @@ export default {
background: white;
border-radius: 0.4rem;
padding: 0.6rem 1.2rem;
- /* box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);*/
border: 0.2rem solid #E3EDFF;
transition: all 0.1s ease;
flex: 1 1 auto;
@@ -210,7 +259,6 @@ export default {
.text-container:hover {
background: #EEF4FF;
border: 0.2rem solid #8DB3FE;
- /*transform: translateY(2px);*/
transform: scale(1.01);
}
@@ -224,8 +272,16 @@ export default {
}
.input-field-tariffrate {
- min-width: 10rem;
+ min-width: 20rem;
flex: 0 1 10rem;
}
+/* Optimierung für kleinere Bildschirme unter 1500px - nur wenn responsive aktiviert ist */
+@media (max-width: 1500px) {
+ .container.responsive .input-field-tariffrate {
+ min-width: auto;
+ flex: 1 1 auto;
+ }
+}
+
\ No newline at end of file
diff --git a/src/frontend/src/components/layout/edit/PackagingEdit.vue b/src/frontend/src/components/layout/edit/PackagingEdit.vue
index 1df58c0..731f35f 100644
--- a/src/frontend/src/components/layout/edit/PackagingEdit.vue
+++ b/src/frontend/src/components/layout/edit/PackagingEdit.vue
@@ -1,14 +1,16 @@
-
+
+
Length
-
Dimension unit
-
+
Dimension unit
+
@@ -16,7 +18,8 @@
Width
@@ -27,7 +30,8 @@
Height
@@ -38,7 +42,8 @@
Weight
@@ -50,7 +55,8 @@
Pieces per HU
@@ -115,6 +121,10 @@ export default {
stackable: {
type: Boolean,
required: true,
+ },
+ responsive: {
+ type: Boolean,
+ default: true,
}
},
computed: {
@@ -153,14 +163,14 @@ export default {
const unitType = this.huDimensionUnits.find(unit => unit.id === value)?.value;
const decimals = (unitType === 'cm') ? 2 : ((unitType === 'm') ? 3 : 0);
- const parsedLength = (this.length ?? null) === null ? null : parseFloat(this.length.toFixed(decimals));
- this.$emit('update:length', parsedLength);
+ const parsedLength = (this.length ?? null) === null ? null : parseFloat(this.length.toFixed(decimals));
+ this.$emit('update:length', parsedLength);
- const parsedHeight = (this.height ?? null) === null ? null : parseFloat(this.height.toFixed(decimals));
- this.$emit('update:height', parsedHeight);
+ const parsedHeight = (this.height ?? null) === null ? null : parseFloat(this.height.toFixed(decimals));
+ this.$emit('update:height', parsedHeight);
- const parsedWidth = (this.width ?? null) === null ? null : parseFloat(this.width.toFixed(decimals));
- this.$emit('update:width', parsedWidth);
+ const parsedWidth = (this.width ?? null) === null ? null : parseFloat(this.width.toFixed(decimals));
+ this.$emit('update:width', parsedWidth);
this.$emit('update:dimensionUnit', unitType);
@@ -187,6 +197,23 @@ export default {
}
},
methods: {
+ handleEnter(currentRef, event) {
+ event.preventDefault();
+
+ // Define the navigation order
+ const inputOrder = ['lengthInput', 'widthInput', 'heightInput', 'weightInput', 'unitCountInput'];
+
+ const currentIndex = inputOrder.indexOf(currentRef);
+ if (currentIndex !== -1 && currentIndex < inputOrder.length - 1) {
+ const nextRef = inputOrder[currentIndex + 1];
+ this.$nextTick(() => {
+ if (this.$refs[nextRef]) {
+ this.$refs[nextRef].focus();
+ this.$refs[nextRef].select();
+ }
+ });
+ }
+ },
focusLost(event) {
if (!this.$el.contains(event.relatedTarget)) {
this.$emit('save', 'packaging');
@@ -252,10 +279,127 @@ export default {
\ No newline at end of file
diff --git a/src/frontend/src/pages/CalculationMassEdit.vue b/src/frontend/src/pages/CalculationMassEdit.vue
index 9490923..d89e788 100644
--- a/src/frontend/src/pages/CalculationMassEdit.vue
+++ b/src/frontend/src/pages/CalculationMassEdit.vue
@@ -86,6 +86,7 @@
v-model:stackable="componentProps.stackable"
v-model:preSelectedNode="componentProps.preSelectedNode"
v-model:openSelectDirect="componentProps.openSelectDirect"
+ :responsive="false"
@update-material="updateMaterial"
@update-supplier="updateSupplier"
@close="closeEditModalAction('cancel')"
@@ -318,7 +319,7 @@ export default {
if (id === -1) {
// clear
this.componentsData = {
- price: {props: {price: 0, overSeaShare: 0.0, includeFcaFee: true}},
+ price: {props: {price: 0, overSeaShare: 0.0, includeFcaFee: false}},
material: {props: {partNumber: "", hsCode: "", tariffRate: 0.00, description: "", openSelectDirect: true}},
packaging: {
props: {
diff --git a/src/frontend/src/pages/CalculationSingleEdit.vue b/src/frontend/src/pages/CalculationSingleEdit.vue
index 66aa6c2..5077ddd 100644
--- a/src/frontend/src/pages/CalculationSingleEdit.vue
+++ b/src/frontend/src/pages/CalculationSingleEdit.vue
@@ -28,28 +28,20 @@
-
-
-
+
+
+
+
+
@@ -226,41 +229,29 @@ export default {
flex-wrap: wrap;
}
+.supplier-container {
+ margin: 2.4rem 0;
+}
+
+.supplier-item {
+ width: 100%;
+}
+
.master-data-container {
display: grid;
- grid-template-columns: 1fr 1fr 1fr;
- grid-template-rows: auto auto;
+ grid-template-columns: 4fr 4fr 5fr;
gap: 1.6rem;
margin: 2.4rem 0;
}
-.master-data-stretched-item {
- grid-row: span 2 / span 2;
-}
-
-.master-data-packaging {
- grid-column-start: 3;
- grid-row-start: 1;
+.master-data-item {
+ padding: 2.4rem;
}
/* Responsive layout for viewports below 1500px */
@media (max-width: 1500px) {
.master-data-container {
grid-template-columns: 1fr;
- grid-template-rows: auto;
- }
-
- .master-data-stretched-item {
- grid-row: auto;
- }
-
- .master-data-packaging {
- grid-column-start: auto;
- grid-row-start: auto;
- }
-
- .master-data-item {
- padding: 2.4rem;
}
}
diff --git a/src/frontend/src/pages/DevPage.vue b/src/frontend/src/pages/DevPage.vue
new file mode 100644
index 0000000..6e9ec16
--- /dev/null
+++ b/src/frontend/src/pages/DevPage.vue
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/frontend/src/router.js b/src/frontend/src/router.js
index 7e8d3a7..2d001ac 100644
--- a/src/frontend/src/router.js
+++ b/src/frontend/src/router.js
@@ -6,7 +6,8 @@ import CalculationSingleEdit from "@/pages/CalculationSingleEdit.vue";
import CalculationMassEdit from "@/pages/CalculationMassEdit.vue";
import CalculationAssistant from "@/pages/CalculationAssistant.vue";
import ErrorLog from "@/pages/ErrorLog.vue";
-import CalcualtionDump from "@/pages/CalcualtionDump.vue";
+import CalculationDump from "@/components/layout/dev/CalculationDump.vue";
+import DevPage from "@/pages/DevPage.vue";
const router = createRouter({
history: createWebHistory(),
@@ -56,10 +57,16 @@ const router = createRouter({
},
{
- path: '/error/dump/:id',
- component: CalcualtionDump
+ path: '/dev/dump/:id',
+ component: CalculationDump,
+ name: 'dev-calculation-dump'
},
+ {
+ path: '/dev',
+ component: DevPage,
+ name: 'dev-page',
+ },
{
path: '/:pathMatch(.*)*',
redirect: '/calculations'
diff --git a/src/main/java/de/avatic/lcc/config/CorsConfig.java b/src/main/java/de/avatic/lcc/config/CorsConfig.java
index cd65c2e..9b5ec5d 100644
--- a/src/main/java/de/avatic/lcc/config/CorsConfig.java
+++ b/src/main/java/de/avatic/lcc/config/CorsConfig.java
@@ -43,7 +43,7 @@ public class CorsConfig implements WebMvcConfigurer {
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.exposedHeaders("X-Total-Count", "X-Page-Count", "X-Current-Page")
- .allowCredentials(false);
+ .allowCredentials(true);
} else {
// Production CORS configuration
registry.addMapping("/api/**")
diff --git a/src/main/java/de/avatic/lcc/config/SecurityConfig.java b/src/main/java/de/avatic/lcc/config/SecurityConfig.java
index d24de23..b88713c 100644
--- a/src/main/java/de/avatic/lcc/config/SecurityConfig.java
+++ b/src/main/java/de/avatic/lcc/config/SecurityConfig.java
@@ -3,22 +3,34 @@ package de.avatic.lcc.config;
import de.avatic.lcc.model.users.User;
import de.avatic.lcc.repositories.users.GroupRepository;
import de.avatic.lcc.repositories.users.UserRepository;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.jetbrains.annotations.NotNull;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
-import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
+import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
+import org.springframework.security.web.csrf.CsrfToken;
+import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
+import org.springframework.security.web.csrf.CsrfTokenRequestHandler;
+import org.springframework.util.StringUtils;
+import org.springframework.web.filter.OncePerRequestFilter;
+import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
+import java.util.function.Supplier;
@Configuration
public class SecurityConfig {
@@ -28,11 +40,18 @@ public class SecurityConfig {
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
+ .requestMatchers("/api/**").authenticated()
.anyRequest().authenticated()
)
.oauth2Login(oauth2 -> oauth2
.defaultSuccessUrl("/", true)
- );
+
+ )
+ .csrf(csrf -> csrf
+ .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
+ .csrfTokenRequestHandler(new LccCsrfTokenRequestHandler())
+ )
+ .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class);
return http.build();
}
@@ -42,7 +61,11 @@ public class SecurityConfig {
public SecurityFilterChain devSecurityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
- .csrf(AbstractHttpConfigurer::disable)
+ .csrf(csrf -> csrf
+ .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
+ .csrfTokenRequestHandler(new LccCsrfTokenRequestHandler())
+ )
+ .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class)
.build();
}
@@ -55,7 +78,7 @@ public class SecurityConfig {
OidcUser oidcUser = delegate.loadUser(userRequest);
Integer userId = null;
-// // Debug: Print all claims
+ // Debug: Print all claims
// System.out.println("=== ID Token Claims ===");
// oidcUser.getIdToken().getClaims().forEach((key, value) ->
// System.out.println(key + ": " + value)
@@ -64,6 +87,15 @@ public class SecurityConfig {
Set mappedAuthorities = new HashSet<>(oidcUser.getAuthorities());
+ String workdayId = oidcUser.getAttribute("workday_id");
+ if (workdayId != null) {
+ User user = userRepository.getByWorkdayId(workdayId);
+ if (user != null) {
+ user.getGroups().forEach(group -> mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + group.getName())));
+ userId = user.getId();
+ }
+ }
+
// Try different ways to get email
String email = oidcUser.getEmail();
if (email == null) {
@@ -79,10 +111,10 @@ public class SecurityConfig {
if (email != null) {
User user = userRepository.getByEmail(email);
if (user != null) {
- user.getGroups().forEach(group -> mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + group.getName())));
+ user.getGroups().forEach(group -> mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + group.getName().toUpperCase())));
userId = user.getId();
} else {
- mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_default"));
+ mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_BASIC"));
}
}
@@ -95,4 +127,35 @@ public class SecurityConfig {
);
};
}
+
+
+ public static final class LccCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler {
+ private final CsrfTokenRequestHandler delegate = new CsrfTokenRequestAttributeHandler();
+
+ @Override
+ public void handle(HttpServletRequest request, HttpServletResponse response,
+ Supplier csrfToken) {
+ this.delegate.handle(request, response, csrfToken);
+ }
+
+ @Override
+ public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
+ if (StringUtils.hasText(request.getHeader(csrfToken.getHeaderName()))) {
+ return super.resolveCsrfTokenValue(request, csrfToken);
+ }
+ return this.delegate.resolveCsrfTokenValue(request, csrfToken);
+ }
+ }
+
+ public static final class CsrfCookieFilter extends OncePerRequestFilter {
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response,
+ @NotNull FilterChain filterChain) throws ServletException, IOException {
+ CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
+ if (csrfToken != null) {
+ csrfToken.getToken();
+ }
+ filterChain.doFilter(request, response);
+ }
+ }
}
diff --git a/src/main/java/de/avatic/lcc/controller/ErrorController.java b/src/main/java/de/avatic/lcc/controller/ErrorController.java
index ec80f73..68a9c2e 100644
--- a/src/main/java/de/avatic/lcc/controller/ErrorController.java
+++ b/src/main/java/de/avatic/lcc/controller/ErrorController.java
@@ -18,11 +18,9 @@ import java.util.Optional;
public class ErrorController {
private final SysErrorService sysErrorService;
- private final DumpRepository dumpRepository;
- public ErrorController(SysErrorService sysErrorService, DumpRepository dumpRepository) {
+ public ErrorController(SysErrorService sysErrorService) {
this.sysErrorService = sysErrorService;
- this.dumpRepository = dumpRepository;
}
@PostMapping
@@ -43,9 +41,6 @@ public class ErrorController {
.body(errors.toList());
}
- @GetMapping({"/dump/{id}", "/dump/{id}/"})
- public ResponseEntity getDump(@PathVariable Integer id) {
- return ResponseEntity.ok(dumpRepository.getDump(id));
- }
+
}
diff --git a/src/main/java/de/avatic/lcc/controller/dev/DevController.java b/src/main/java/de/avatic/lcc/controller/dev/DevController.java
new file mode 100644
index 0000000..d580bab
--- /dev/null
+++ b/src/main/java/de/avatic/lcc/controller/dev/DevController.java
@@ -0,0 +1,76 @@
+package de.avatic.lcc.controller.dev;
+
+import com.azure.core.annotation.BodyParam;
+import de.avatic.lcc.dto.error.CalculationJobDumpDTO;
+import de.avatic.lcc.dto.users.UserDTO;
+import de.avatic.lcc.repositories.error.DumpRepository;
+import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
+import de.avatic.lcc.repositories.pagination.SearchQueryResult;
+import de.avatic.lcc.repositories.users.UserRepository;
+import de.avatic.lcc.service.transformer.users.UserTransformer;
+import de.avatic.lcc.service.users.authorization.AuthorizationService;
+import jakarta.validation.constraints.Min;
+import org.springframework.context.annotation.Profile;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@Profile("dev")
+@RequestMapping({"/api/dev", "/api/dev/"})
+public class DevController {
+
+ private final AuthorizationService authorizationService;
+ private final DumpRepository dumpRepository;
+ private final UserRepository userRepository;
+ private final UserTransformer userTransformer;
+
+ public DevController(AuthorizationService authorizationService, DumpRepository dumpRepository, UserRepository userRepository, UserTransformer userTransformer) {
+ this.authorizationService = authorizationService;
+ this.dumpRepository = dumpRepository;
+ this.userRepository = userRepository;
+ this.userTransformer = userTransformer;
+ }
+
+ @GetMapping({"/dump/{id}", "/dump/{id}/"})
+ public ResponseEntity getDump(@PathVariable Integer id) {
+ return ResponseEntity.ok(dumpRepository.getDump(id));
+ }
+
+ @GetMapping({"/dump/", "/dump"})
+ public ResponseEntity> listDumps(
+ @RequestParam(defaultValue = "20") @Min(1) int limit,
+ @RequestParam(defaultValue = "1") @Min(1) int page) {
+
+ var dump = dumpRepository.listDumps(new SearchQueryPagination(page, limit));
+
+ return ResponseEntity.ok()
+ .header("X-Total-Count", String.valueOf(dump.getTotalElements()))
+ .header("X-Page-Count", String.valueOf(dump.getTotalPages()))
+ .header("X-Current-Page", String.valueOf(page))
+ .body(dump.toList());
+ }
+
+ @GetMapping({"/user"})
+ public ResponseEntity> listUser(@RequestParam(defaultValue = "20") @Min(1) int limit,
+ @RequestParam(defaultValue = "1") @Min(1) int page) {
+
+ var users = SearchQueryResult.map(userRepository.listUsers(new SearchQueryPagination(page, limit)), userTransformer::toUserDTO);
+
+ return ResponseEntity.ok()
+ .header("X-Total-Count", String.valueOf(users.getTotalElements()))
+ .header("X-Page-Count", String.valueOf(users.getTotalPages()))
+ .header("X-Current-Page", String.valueOf(page))
+ .body(users.toList());
+
+ }
+
+ @PostMapping({"/user", "/user/"})
+ public ResponseEntity setActiveUser(@RequestBody UserDTO user) {
+ authorizationService.setActiveUser(user.getEmail());
+ return ResponseEntity.ok().build();
+ }
+
+
+}
diff --git a/src/main/java/de/avatic/lcc/controller/users/ActiveUserController.java b/src/main/java/de/avatic/lcc/controller/users/ActiveUserController.java
new file mode 100644
index 0000000..243e909
--- /dev/null
+++ b/src/main/java/de/avatic/lcc/controller/users/ActiveUserController.java
@@ -0,0 +1,29 @@
+package de.avatic.lcc.controller.users;
+
+import de.avatic.lcc.dto.users.UserDTO;
+import de.avatic.lcc.service.transformer.users.UserTransformer;
+import de.avatic.lcc.service.users.authorization.AuthorizationService;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping({"/api/active-user", "/api/active-user/"})
+public class ActiveUserController {
+
+
+ private final AuthorizationService authorizationService;
+ private final UserTransformer userTransformer;
+
+ public ActiveUserController(AuthorizationService authorizationService, UserTransformer userTransformer) {
+ this.authorizationService = authorizationService;
+ this.userTransformer = userTransformer;
+ }
+
+ @GetMapping
+ public ResponseEntity getActiveUser() {
+ return ResponseEntity.ok(userTransformer.toUserDTO(authorizationService.getActiveUser()));
+ }
+
+}
diff --git a/src/main/java/de/avatic/lcc/repositories/error/DumpRepository.java b/src/main/java/de/avatic/lcc/repositories/error/DumpRepository.java
index 3a77559..9082fb5 100644
--- a/src/main/java/de/avatic/lcc/repositories/error/DumpRepository.java
+++ b/src/main/java/de/avatic/lcc/repositories/error/DumpRepository.java
@@ -6,9 +6,12 @@ import de.avatic.lcc.dto.error.CalculationJobRouteSectionDumpDTO;
import de.avatic.lcc.dto.error.ErrorLogDTO;
import de.avatic.lcc.dto.error.ErrorLogTraceItemDto;
import de.avatic.lcc.dto.calculation.edit.PremiseDetailDTO;
+import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
+import de.avatic.lcc.repositories.pagination.SearchQueryResult;
import de.avatic.lcc.repositories.premise.PremiseRepository;
import de.avatic.lcc.service.transformer.premise.PremiseTransformer;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Profile;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
@@ -52,29 +55,7 @@ public class DumpRepository {
CalculationJobDumpDTO dump = namedParameterJdbcTemplate.queryForObject(
calculationJobQuery,
params,
- (rs, rowNum) -> {
- CalculationJobDumpDTO dto = new CalculationJobDumpDTO();
- dto.setId(rs.getInt("id"));
- dto.setPremiseId(rs.getInt("premise_id"));
-
- Timestamp calculationDate = rs.getTimestamp("calculation_date");
- if (calculationDate != null) {
- dto.setCalculationDate(calculationDate.toString());
- }
-
- dto.setValidityPeriodId(rs.getInt("validity_period_id"));
- dto.setPropertySetId(rs.getInt("property_set_id"));
- dto.setJobState(rs.getString("job_state"));
- dto.setUserId(rs.getInt("user_id"));
-
- // Check if there's an error_id
- Integer errorId = rs.getObject("error_id", Integer.class);
- if (errorId != null) {
- dto.setErrorLog(loadErrorLog(errorId));
- }
-
- return dto;
- });
+ new CalculationMapper());
// Load premise details
dump.setPremise(loadPremiseDetails(dump.getPremiseId()));
@@ -229,28 +210,7 @@ public class DumpRepository {
CalculationJobDumpDTO dump = jdbcTemplate.queryForObject(
calculationJobQuery,
- (rs, rowNum) -> {
- CalculationJobDumpDTO dto = new CalculationJobDumpDTO();
- dto.setId(rs.getInt("id"));
- dto.setPremiseId(rs.getInt("premise_id"));
-
- Timestamp calculationDate = rs.getTimestamp("calculation_date");
- if (calculationDate != null) {
- dto.setCalculationDate(calculationDate.toString());
- }
-
- dto.setValidityPeriodId(rs.getInt("validity_period_id"));
- dto.setPropertySetId(rs.getInt("property_set_id"));
- dto.setJobState(rs.getString("job_state"));
- dto.setUserId(rs.getInt("user_id"));
-
- Integer errorId = rs.getObject("error_id", Integer.class);
- if (errorId != null) {
- dto.setErrorLog(loadErrorLogAlternative(errorId));
- }
-
- return dto;
- },
+ new CalculationMapper(),
id // Parameter passed directly without Object array
);
@@ -307,6 +267,92 @@ public class DumpRepository {
);
}
+ public SearchQueryResult listDumps(SearchQueryPagination searchQueryPagination) {
+
+ String calculationJobQuery = """
+ SELECT cj.id, cj.premise_id, cj.calculation_date, cj.validity_period_id,
+ cj.property_set_id, cj.job_state, cj.error_id, cj.user_id
+ FROM calculation_job cj
+ ORDER BY id DESC LIMIT :limit OFFSET :offset
+ """;
+
+ MapSqlParameterSource params = new MapSqlParameterSource();
+ params.addValue("offset", searchQueryPagination.getOffset());
+ params.addValue("limit", searchQueryPagination.getLimit());
+
+ var dumps = namedParameterJdbcTemplate.query(
+ calculationJobQuery,
+ params,
+ (rs, _) -> {
+ CalculationJobDumpDTO dto = new CalculationJobDumpDTO();
+ dto.setId(rs.getInt("id"));
+ dto.setPremiseId(rs.getInt("premise_id"));
+
+ Timestamp calculationDate = rs.getTimestamp("calculation_date");
+ if (calculationDate != null) {
+ dto.setCalculationDate(calculationDate.toString());
+ }
+
+ dto.setValidityPeriodId(rs.getInt("validity_period_id"));
+ dto.setPropertySetId(rs.getInt("property_set_id"));
+ dto.setJobState(rs.getString("job_state"));
+ dto.setUserId(rs.getInt("user_id"));
+
+ // Check if there's an error_id
+ Integer errorId = rs.getObject("error_id", Integer.class);
+ if (errorId != null) {
+ dto.setErrorLog(loadErrorLog(errorId));
+ }
+
+ return dto;
+ });
+
+ for(var dump : dumps) {
+ // Load premise details
+ dump.setPremise(loadPremiseDetails(dump.getPremiseId()));
+
+ // Load destinations
+ dump.setDestinations(loadCalculationJobDestinations(dump.getId()));
+
+ }
+
+ return new SearchQueryResult<>(dumps, searchQueryPagination.getPage(),countCalculations(), searchQueryPagination.getLimit());
+ }
+
+ private class CalculationMapper implements RowMapper {
+
+ @Override
+ public CalculationJobDumpDTO mapRow(ResultSet rs, int rowNum) throws SQLException {
+
+ CalculationJobDumpDTO dto = new CalculationJobDumpDTO();
+ dto.setId(rs.getInt("id"));
+ dto.setPremiseId(rs.getInt("premise_id"));
+
+ Timestamp calculationDate = rs.getTimestamp("calculation_date");
+ if (calculationDate != null) {
+ dto.setCalculationDate(calculationDate.toString());
+ }
+
+ dto.setValidityPeriodId(rs.getInt("validity_period_id"));
+ dto.setPropertySetId(rs.getInt("property_set_id"));
+ dto.setJobState(rs.getString("job_state"));
+ dto.setUserId(rs.getInt("user_id"));
+
+ // Check if there's an error_id
+ Integer errorId = rs.getObject("error_id", Integer.class);
+ if (errorId != null) {
+ dto.setErrorLog(loadErrorLog(errorId));
+ }
+
+ return dto;
+
+ }
+ }
+
+ private Integer countCalculations() {
+ return jdbcTemplate.queryForObject("SELECT COUNT(*) FROM calculation_job", Integer.class);
+ }
+
private static class CalculationJobDestinationRowMapper implements RowMapper {
@Override
public CalculationJobDestinationDumpDTO mapRow(ResultSet rs, int rowNum) throws SQLException {
diff --git a/src/main/java/de/avatic/lcc/repositories/users/UserRepository.java b/src/main/java/de/avatic/lcc/repositories/users/UserRepository.java
index c40434f..7be841e 100644
--- a/src/main/java/de/avatic/lcc/repositories/users/UserRepository.java
+++ b/src/main/java/de/avatic/lcc/repositories/users/UserRepository.java
@@ -63,7 +63,7 @@ public class UserRepository {
@Transactional
public void update(User user) {
- Integer userId = findUserId(user.getWorkdayId());
+ Integer userId = getUserIdByWorkdayId(user.getWorkdayId());
List groupIds = findGroupIds(user.getGroups().stream().map(Group::getName).toList());
@@ -141,7 +141,8 @@ public class UserRepository {
}
- private Integer findUserId(String workdayId) {
+ @Transactional
+ public Integer getUserIdByWorkdayId(String workdayId) {
List results = jdbcTemplate.query("SELECT id FROM sys_user WHERE workday_id = ?",
(rs, rowNum) -> rs.getInt("id"),
workdayId);
@@ -149,6 +150,15 @@ public class UserRepository {
return results.isEmpty() ? null : results.getFirst();
}
+ @Transactional
+ public User getByWorkdayId(String workdayId) {
+ List results = jdbcTemplate.query("SELECT id FROM sys_user WHERE workday_id = ?",
+ new UserMapper(),
+ workdayId);
+
+ return results.isEmpty() ? null : results.getFirst();
+ }
+
@Transactional
public User getById(Integer id) {
String query = """
diff --git a/src/main/java/de/avatic/lcc/service/access/PremisesService.java b/src/main/java/de/avatic/lcc/service/access/PremisesService.java
index 24fc0eb..370d6b6 100644
--- a/src/main/java/de/avatic/lcc/service/access/PremisesService.java
+++ b/src/main/java/de/avatic/lcc/service/access/PremisesService.java
@@ -88,12 +88,6 @@ public class PremisesService {
//TODO use actual user.
userId = 1;
- Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
-
- //todo make a service. and simulate user rights in dev profile.
- if (authentication != null && authentication.getPrincipal() instanceof LccOidcUser) {
- LccOidcUser oidcUser = (LccOidcUser) authentication.getPrincipal();
- }
return SearchQueryResult.map(premiseRepository.listPremises(filter, new SearchQueryPagination(page, limit), userId, deleted, archived, done), admin ? premiseTransformer::toPremiseDTOWithUserInfo : premiseTransformer::toPremiseDTO);
diff --git a/src/main/java/de/avatic/lcc/service/users/UserService.java b/src/main/java/de/avatic/lcc/service/users/UserService.java
index d01c255..2adedd2 100644
--- a/src/main/java/de/avatic/lcc/service/users/UserService.java
+++ b/src/main/java/de/avatic/lcc/service/users/UserService.java
@@ -1,10 +1,13 @@
package de.avatic.lcc.service.users;
+import de.avatic.lcc.config.LccOidcUser;
import de.avatic.lcc.dto.users.UserDTO;
import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
import de.avatic.lcc.repositories.users.UserRepository;
import de.avatic.lcc.service.transformer.users.UserTransformer;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
/**
@@ -42,4 +45,21 @@ public class UserService {
userRepository.update(userTransformer.fromUserDTO(user));
}
+ public boolean isSuper() {
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+
+ //todo make a service. and simulate user rights in dev profile.
+ if (authentication != null && authentication.getPrincipal() instanceof LccOidcUser) {
+ LccOidcUser oidcUser = (LccOidcUser) authentication.getPrincipal();
+
+ return oidcUser.getAuthorities().stream().anyMatch(authority -> authority.getAuthority().equals("ROLE_SUPER"));
+ }
+
+ return false;
+ }
+
+// public boolean canCalculate() {
+// Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+// }
+
}
diff --git a/src/main/java/de/avatic/lcc/service/users/authorization/AuthorizationService.java b/src/main/java/de/avatic/lcc/service/users/authorization/AuthorizationService.java
new file mode 100644
index 0000000..3370564
--- /dev/null
+++ b/src/main/java/de/avatic/lcc/service/users/authorization/AuthorizationService.java
@@ -0,0 +1,12 @@
+package de.avatic.lcc.service.users.authorization;
+
+import de.avatic.lcc.dto.users.UserDTO;
+import de.avatic.lcc.model.users.User;
+import org.springframework.stereotype.Service;
+
+@Service
+public interface AuthorizationService {
+ default void setActiveUser(String id) { throw new UnsupportedOperationException(); }
+
+ User getActiveUser();
+}
diff --git a/src/main/java/de/avatic/lcc/service/users/authorization/DefaultAuthorizationService.java b/src/main/java/de/avatic/lcc/service/users/authorization/DefaultAuthorizationService.java
new file mode 100644
index 0000000..54f8f6f
--- /dev/null
+++ b/src/main/java/de/avatic/lcc/service/users/authorization/DefaultAuthorizationService.java
@@ -0,0 +1,15 @@
+package de.avatic.lcc.service.users.authorization;
+
+
+import de.avatic.lcc.model.users.User;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Service;
+
+@Service
+@Profile("!dev & !test")
+public class DefaultAuthorizationService implements AuthorizationService{
+ @Override
+ public User getActiveUser() {
+ return null;
+ }
+}
diff --git a/src/main/java/de/avatic/lcc/service/users/authorization/SimulatedAuthorizationService.java b/src/main/java/de/avatic/lcc/service/users/authorization/SimulatedAuthorizationService.java
new file mode 100644
index 0000000..fb866fe
--- /dev/null
+++ b/src/main/java/de/avatic/lcc/service/users/authorization/SimulatedAuthorizationService.java
@@ -0,0 +1,36 @@
+package de.avatic.lcc.service.users.authorization;
+
+import de.avatic.lcc.dto.users.UserDTO;
+import de.avatic.lcc.model.users.User;
+import de.avatic.lcc.repositories.users.UserRepository;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Service;
+
+
+@Service
+@Profile("dev | test")
+public class SimulatedAuthorizationService implements AuthorizationService {
+
+ private final UserRepository userRepository;
+ private User activeUser;
+
+ public SimulatedAuthorizationService(UserRepository userRepository) {
+ this.userRepository = userRepository;
+ }
+
+ @Override
+ public void setActiveUser(String mail) {
+ this.activeUser = userRepository.getByEmail(mail);
+ System.out.println(activeUser);
+ }
+
+ @Override
+ public User getActiveUser() {
+ if(activeUser == null){
+ activeUser = userRepository.getById(1);
+ }
+
+ return activeUser;
+ }
+
+}
diff --git a/src/test/resources/master_data/users.sql b/src/test/resources/master_data/users.sql
index 93dc08b..1e18f04 100644
--- a/src/test/resources/master_data/users.sql
+++ b/src/test/resources/master_data/users.sql
@@ -8,16 +8,16 @@ VALUES ('USR001', 'john.doe@company.com', 'John', 'Doe', TRUE),
ON DUPLICATE KEY UPDATE email = VALUES(email);
INSERT INTO sys_group(group_name, group_description)
-VALUES ('default', 'Default user: Can login and generate reports');
+VALUES ('basic', 'Login, generate reports');
INSERT INTO sys_group(group_name, group_description)
-VALUES ('LCE', 'Logistic cost expert: Can login, generate reports and do calculations');
+VALUES ('calculation', 'Login, generate reports, do calculations');
INSERT INTO sys_group(group_name, group_description)
-VALUES ('freight', 'Freight key user: Can login, generate reports and edit freight rates');
+VALUES ('freight', 'Login, generate reports, edit freight rates');
INSERT INTO sys_group(group_name, group_description)
-VALUES ('packaging', 'Packaging key user: Can login, generate reports and edit packaging data');
+VALUES ('packaging', 'Login, generate reports, edit packaging data');
INSERT INTO sys_group(group_name, group_description)
VALUES ('super',
- 'Super key user: Can login, generate reports, do calculations, edit freight rates, edit packaging data');
+ 'Login, generate reports, do calculations, edit freight rates, edit packaging data');
INSERT INTO sys_user_group_mapping (user_id, group_id)
VALUES ((SELECT id FROM sys_group WHERE group_name = 'LCE'),