From be7b94753e28785ec9a9c4115a24646e09fd457e Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 20 Nov 2025 10:47:05 +0100 Subject: [PATCH] Removed unused customs store and related functionality, deprecated Toast component, optimized tariff rate modal behavior, and added HS code index updates in the database schema. Enhanced user group management APIs, refined material update handling, improved input validation in user management UI, reorganized modal styles, and addressed redundant logic across services and components. --- .../src/components/layout/config/EditUser.vue | 90 ++++++++++-------- .../src/pages/CalculationMassEdit.vue | 91 +++---------------- src/frontend/src/store/customs.js | 56 ------------ src/frontend/src/store/premiseEdit.js | 11 +-- .../lcc/controller/SessionController.java | 23 ----- .../calculation/PremiseController.java | 4 +- .../lcc/controller/users/UserController.java | 22 +++++ .../de/avatic/lcc/dto/users/GroupDTO.java | 10 ++ .../java/de/avatic/lcc/dto/users/UserDTO.java | 7 +- .../lcc/service/access/PremisesService.java | 2 + .../calculation/PremiseCreationService.java | 2 +- .../PremiseSearchStringAnalyzerService.java | 9 ++ .../transformer/users/UserTransformer.java | 2 +- .../lcc/service/users/GroupService.java | 1 + .../avatic/lcc/service/users/UserService.java | 49 ++++++++-- .../badrequest/NotFoundException.java | 3 +- .../db/migration/V1__Create_schema.sql | 3 + src/main/resources/schema.sql | 7 +- 18 files changed, 178 insertions(+), 214 deletions(-) delete mode 100644 src/frontend/src/store/customs.js delete mode 100644 src/main/java/de/avatic/lcc/controller/SessionController.java diff --git a/src/frontend/src/components/layout/config/EditUser.vue b/src/frontend/src/components/layout/config/EditUser.vue index 04ef116..3840b48 100644 --- a/src/frontend/src/components/layout/config/EditUser.vue +++ b/src/frontend/src/components/layout/config/EditUser.vue @@ -3,20 +3,21 @@
Workday ID
-
+
+
Firstname
-
+
Lastname
-
+
E-Mail
-
+
Groups
@@ -62,6 +63,11 @@ export default { } }, methods: { + validate(type) { + if (type === "firstname") { + firstName + } + }, closeModal(save) { this.$emit("close", save); }, @@ -82,7 +88,7 @@ export default { } else { if ((idx ?? null) !== null && idx !== -1) - this.user.groups.splice(idx,1); + this.user.groups.splice(idx, 1); } }, @@ -122,22 +128,13 @@ 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 136712a..ae2474b 100644 --- a/src/frontend/src/pages/CalculationMassEdit.vue +++ b/src/frontend/src/pages/CalculationMassEdit.vue @@ -18,7 +18,6 @@
- @@ -64,6 +63,7 @@ v-model:partNumber="componentProps.partNumber" v-model:hsCode="componentProps.hsCode" v-model:tariffRate="componentProps.tariffRate" + v-model:tariffUnlocked="componentProps.tariffUnlocked" v-model:description="componentProps.description" v-model:price="componentProps.price" v-model:overSeaShare="componentProps.overSeaShare" @@ -81,13 +81,12 @@ :countryId=null :responsive="false" @close="closeEditModalAction('cancel')" - @start-lookup="doLookupOnClose" > - @@ -113,9 +112,7 @@ import PriceEdit from "@/components/layout/edit/PriceEdit.vue"; import MaterialEdit from "@/components/layout/edit/MaterialEdit.vue"; import PackagingEdit from "@/components/layout/edit/PackagingEdit.vue"; import DestinationListView from "@/components/layout/edit/DestinationListView.vue"; -import Toast from "@/components/UI/Toast.vue"; import logger from "@/logger.js"; -import {useCustomsStore} from "@/store/customs.js"; import {useNotificationStore} from "@/store/notification.js"; @@ -129,7 +126,6 @@ const COMPONENT_TYPES = { export default { name: "MassEdit", components: { - Toast, Modal, MassEditDialog, ListEdit, @@ -140,7 +136,7 @@ export default { BasicButton }, computed: { - ...mapStores(usePremiseEditStore, useCustomsStore, useNotificationStore), + ...mapStores(usePremiseEditStore, useNotificationStore), hasSelection() { if (this.premiseEditStore.isLoading || this.premiseEditStore.selectedLoading) { return false; @@ -168,8 +164,8 @@ export default { showMultiselectAction() { return this.selectCount > 0; }, - showModalFooter() { - return this.modalType !== 'supplier'; + modalCloseOnly() { + return this.modalType === 'material' && !this.componentProps.tariffUnlocked; }, showEditModal() { return ((this.modalType ?? null) !== null); @@ -208,13 +204,12 @@ export default { data() { return { - doLookup: false, ids: [], bulkQuery: null, modalType: null, componentsData: { price: {props: {price: 0, overSeaShare: 0, includeFcaFee: false}}, - material: {props: {partNumber: "", hsCode: null, tariffRate: 0.00, description: "", hideDescription: false}}, + material: {props: {partNumber: "", hsCode: null, tariffRate: null, tariffUnlocked: false, description: "", hideDescription: false}}, packaging: { props: { length: 0, @@ -256,16 +251,7 @@ export default { this.showCalculationModal = true; const error = await this.premiseEditStore.startCalculation(); - if (error !== null) { - - this.$refs.toast.addToast({ - icon: 'warning', - message: error.message, - title: "Cannot start calculation", - variant: 'exception', - duration: 8000 - }) - } else { + if (error === null) { this.closeMassEdit() } this.showCalculationModal = false; @@ -298,9 +284,6 @@ export default { logger.info("open modal", massEdit, this.modalType, this.editIds, this.dataSourceId) }, - doLookupOnClose(doLookup) { - this.doLookup = doLookup; - }, async closeEditModalAction(action) { if (this.modalType === "destinations") { if (action === "accept") { @@ -316,11 +299,7 @@ export default { await this.premiseEditStore.batchUpdatePrice(this.editIds, props); break; case "material": - let tariffRates = null; - if (this.doLookup) { - tariffRates = await this.tariffLookUp(props.hsCode); - } - await this.premiseEditStore.batchUpdateMaterial(this.editIds, props, tariffRates); + await this.premiseEditStore.batchUpdateMaterial(this.editIds, props); break; case "packaging": await this.premiseEditStore.batchUpdatePackaging(this.editIds, props); @@ -332,24 +311,6 @@ export default { this.fillData(this.modalType); this.modalType = null; }, - async tariffLookUp(hsCode) { - - const countryIds = this.premiseEditStore.getCountryIdByPremiseIds(this.editIds); - - const query = {hsCode: hsCode, countryIds: [...new Set(Array.from(countryIds.values()))]}; - const tariffRates = await this.customsStore.findTariffRate(query); - - const found = new Map(); - for(const editId of this.editIds) { - const countryId = countryIds.get(editId); - - - found.set(editId, tariffRates.find(rate => rate.countryId === countryId).value); - } - - this.doLookup = false; - return found; - }, fillData(type, id = -1, hideDescription = false) { if (id === -1) { @@ -361,6 +322,7 @@ export default { partNumber: "", hsCode: null, tariffRate: null, + tariffUnlocked: false, description: null, hideDescription: hideDescription } @@ -394,7 +356,8 @@ export default { this.componentsData.material.props = { partNumber: premise.material.part_number, hsCode: premise.hs_code, - tariffRate: premise.tariff_rate ?? 0.00, + tariffRate: premise.tariff_rate ?? null, + tariffUnlocked: premise.tariff_unlocked, description: premise.material.name ?? "", hideDescription: hideDescription } @@ -431,20 +394,13 @@ export default { margin: 3rem; } -.edit-calculation-spinner-container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 3.6rem; - flex: 1 1 auto; - font-size: 1.6rem; -} .modal-content-container { display: flex; flex-direction: column; gap: 1.6rem; + margin-top: 1.6rem; + min-width: 50rem; } .modal-content-footer { @@ -512,23 +468,6 @@ export default { letter-spacing: 0.08rem; } - -.edit-calculation-spinner-container { - display: flex; - align-items: center; - justify-content: center; - flex: 1 1 30rem -} - -.spinner-box { - font-size: 1.6rem; - width: 24rem; - height: 12rem; - display: flex; - justify-content: center; - align-items: center; -} - .edit-calculation-container { display: flex; flex-direction: column; diff --git a/src/frontend/src/store/customs.js b/src/frontend/src/store/customs.js deleted file mode 100644 index ef47527..0000000 --- a/src/frontend/src/store/customs.js +++ /dev/null @@ -1,56 +0,0 @@ -import {defineStore} from 'pinia' -import {config} from '@/config' -import performRequest from "@/backend.js"; - -export const useCustomsStore = defineStore('customs', { - state() { - return { - hsCodes: [], - tariffInfo: null, - loadingTariff: false, - - } - }, - getters: { - showWarning(state) { - return function (countryId) { - const info = state.tariffInfo?.find(t => t.countryId === countryId); - - if ((info ?? null) !== null) { - return info.default || info.measures?.length > 1; - } - - return false; - } - }, - findByCountryId(state) { - return function (countryId) { - return state.tariffInfo?.find(t => t.countryId === countryId); - } - } - }, - actions: { - async findTariffRate(query) { - this.loadingTariff = true; - const url = `${config.backendUrl}/customs?hs_code=${query.hsCode}&country_ids=${query.countryIds}`; - const resp = await performRequest(this, 'GET', url, null).catch(() => { - this.tariffInfo = null; - this.loadingTariff = false; - }) - this.tariffInfo = resp.data; - - this.tariffInfo.forEach(info => info.measures.sort((m1, m2) => { return parseInt(m1.code) - parseInt(m2.code); })); - - this.loadingTariff = false; - return this.tariffInfo; - }, - async findHsCode(query) { - - const url = `${config.backendUrl}/customs/search?hs_code=${query}`; - const resp = await performRequest(this, 'GET', url, null); - this.hsCodes = resp.data; - return this.hsCodes; - - } - } -}); diff --git a/src/frontend/src/store/premiseEdit.js b/src/frontend/src/store/premiseEdit.js index 1a37870..68ea92a 100644 --- a/src/frontend/src/store/premiseEdit.js +++ b/src/frontend/src/store/premiseEdit.js @@ -263,7 +263,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', { return await this.savePrice(ids, priceData); }, - async batchUpdateMaterial(ids, materialData, tariffRates = null) { + async batchUpdateMaterial(ids, materialData) { const updatedPremises = this.premisses.map(p => { if (ids.includes(p.id)) { return { @@ -272,7 +272,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', { ...p.material, }, ...(materialData.hsCode !== null && {hs_code: materialData.hsCode}), - ...((tariffRates !== null) ? {tariff_rate: tariffRates.get(p.id)} : (materialData.tariffRate !== null && {tariff_rate: materialData.tariffRate})) + ...((materialData.tariffRate !== null && {tariff_rate: materialData.tariffRate})) }; } return p; @@ -281,7 +281,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', { - return await this.saveMaterial(ids, materialData, tariffRates); + return await this.saveMaterial(ids, materialData); }, async batchUpdatePackaging(ids, packagingData) { @@ -706,7 +706,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', { return success; }, - async saveMaterial(ids = null, materialData = null, tariffRates = null) { + async saveMaterial(ids = null, materialData = null) { let success = true; const toBeUpdated = this.premisses ? (ids ? (ids.map(id => this.premisses.find(p => String(p.id) === String(id)))) : (this.premisses.filter(p => p.selected))) : null; @@ -717,8 +717,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', { premise_ids: toBeUpdated.map(p => p.id), hs_code: materialData === null ? toBeUpdated[0].hs_code : materialData.hsCode, - tariff_rate: (null !== tariffRates) ? null : materialData === null ? toBeUpdated[0].tariff_rate : materialData.tariffRate, - tariff_rates: (null === tariffRates) ? null : Object.fromEntries(tariffRates), + tariff_rate: (materialData === null ? toBeUpdated[0].tariff_rate : materialData.tariffRate), }; await performRequest(this, 'POST', `${config.backendUrl}/calculation/material/`, body, false).catch(() => { diff --git a/src/main/java/de/avatic/lcc/controller/SessionController.java b/src/main/java/de/avatic/lcc/controller/SessionController.java deleted file mode 100644 index 392bb42..0000000 --- a/src/main/java/de/avatic/lcc/controller/SessionController.java +++ /dev/null @@ -1,23 +0,0 @@ -package de.avatic.lcc.controller; - -import org.springframework.security.core.Authentication; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.Map; - -@RestController -@RequestMapping("/api/session") -public class SessionController { - - @GetMapping("/keepalive") - public Map keepalive(Authentication authentication) { - - return Map.of( - "status", "ok", - "user", authentication.getName(), - "timestamp", System.currentTimeMillis() - ); - } -} \ No newline at end of file diff --git a/src/main/java/de/avatic/lcc/controller/calculation/PremiseController.java b/src/main/java/de/avatic/lcc/controller/calculation/PremiseController.java index 2ac123d..24bc8bd 100644 --- a/src/main/java/de/avatic/lcc/controller/calculation/PremiseController.java +++ b/src/main/java/de/avatic/lcc/controller/calculation/PremiseController.java @@ -78,8 +78,8 @@ public class PremiseController { public ResponseEntity findMaterialsAndSuppliers(@RequestParam String search) { try { - String decodedValue = URLDecoder.decode(search, StandardCharsets.UTF_8); - return ResponseEntity.ok(premiseSearchStringAnalyzerService.findMaterialAndSuppliers(decodedValue)); +// String decodedValue = URLDecoder.decode(search, StandardCharsets.UTF_8); + return ResponseEntity.ok(premiseSearchStringAnalyzerService.findMaterialAndSuppliers(search)); } catch (Exception e) { throw new BadRequestException("Bad string encoding", "Unable to decode request", e); } diff --git a/src/main/java/de/avatic/lcc/controller/users/UserController.java b/src/main/java/de/avatic/lcc/controller/users/UserController.java index ac50c5e..674f98c 100644 --- a/src/main/java/de/avatic/lcc/controller/users/UserController.java +++ b/src/main/java/de/avatic/lcc/controller/users/UserController.java @@ -6,6 +6,8 @@ import de.avatic.lcc.repositories.pagination.SearchQueryResult; import de.avatic.lcc.service.users.UserService; import jakarta.validation.Valid; import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import org.hibernate.validator.constraints.Length; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; @@ -52,6 +54,24 @@ public class UserController { .body(users.toList()); } + + + @PostMapping({"/{workdayId}/groups/{group}/", "/{workdayId}/groups/{group}"}) + @PreAuthorize("hasRole('RIGHT-MANAGEMENT')") + public ResponseEntity appendGroups(@PathVariable String workdayId, @PathVariable String group) { + + userService.appendGroups(workdayId, group); + return ResponseEntity.ok().build(); + } + + @DeleteMapping({"/{workdayId}/groups/{group}/", "/{workdayId}/groups/{group}"}) + @PreAuthorize("hasRole('RIGHT-MANAGEMENT')") + public ResponseEntity deleteGroups(@PathVariable String workdayId, @PathVariable String group) { + + userService.deleteGroups(workdayId, group); + return ResponseEntity.ok().build(); + } + /** * Retrieves a single user by its workday id. * @@ -68,6 +88,8 @@ public class UserController { .body(users); } + + /** * Updates the details of an existing user or creates a new one if it does not exist. * Users are identified by its workday id. If a group from the group membership does not exist diff --git a/src/main/java/de/avatic/lcc/dto/users/GroupDTO.java b/src/main/java/de/avatic/lcc/dto/users/GroupDTO.java index 39fec00..4b2ed4d 100644 --- a/src/main/java/de/avatic/lcc/dto/users/GroupDTO.java +++ b/src/main/java/de/avatic/lcc/dto/users/GroupDTO.java @@ -5,6 +5,8 @@ import com.fasterxml.jackson.annotation.JsonProperty; public class GroupDTO { + private Integer id; + @JsonProperty("group_name") private String name; @@ -26,4 +28,12 @@ public class GroupDTO { public void setDescription(String description) { this.description = description; } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } } diff --git a/src/main/java/de/avatic/lcc/dto/users/UserDTO.java b/src/main/java/de/avatic/lcc/dto/users/UserDTO.java index 8fc22e5..27707ae 100644 --- a/src/main/java/de/avatic/lcc/dto/users/UserDTO.java +++ b/src/main/java/de/avatic/lcc/dto/users/UserDTO.java @@ -4,6 +4,7 @@ package de.avatic.lcc.dto.users; import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.validation.Valid; import jakarta.validation.constraints.*; +import org.hibernate.validator.constraints.Length; import java.util.List; @@ -11,18 +12,22 @@ public class UserDTO { @NotNull @JsonProperty("firstname") + @Length(max = 100, min= 1, message = "Firstname must be max 100 characters long") private String firstName; @NotNull @JsonProperty("lastname") + @Length(max = 100, min= 1, message = "Lastname must be max 100 characters long") private String lastName; - @Email + @Email(message = "Mail must be valid") @JsonProperty("mail") + @Length(max = 254, min = 1, message = "Email must be max 254 characters long") private String email; @NotNull @JsonProperty("workday_id") + @Length(max = 32, message = "Workday ID must be max 32 characters long") private String workdayId; @NotNull 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 d29f5ee..3ca68f5 100644 --- a/src/main/java/de/avatic/lcc/service/access/PremisesService.java +++ b/src/main/java/de/avatic/lcc/service/access/PremisesService.java @@ -214,6 +214,8 @@ public class PremisesService { var unlockedIds = premiseRepository.getIdsWithUnlockedTariffs(materialUpdateDTO.getPremiseIds()); + if (unlockedIds.isEmpty()) return; + var tariffRate = materialUpdateDTO.getTariffRate() == null ? null : BigDecimal.valueOf(materialUpdateDTO.getTariffRate().doubleValue()); premiseRepository.updateMaterial(unlockedIds, materialUpdateDTO.getHsCode(), tariffRate); diff --git a/src/main/java/de/avatic/lcc/service/calculation/PremiseCreationService.java b/src/main/java/de/avatic/lcc/service/calculation/PremiseCreationService.java index 5a2e2f4..e3bd5ab 100644 --- a/src/main/java/de/avatic/lcc/service/calculation/PremiseCreationService.java +++ b/src/main/java/de/avatic/lcc/service/calculation/PremiseCreationService.java @@ -128,7 +128,7 @@ public class PremiseCreationService { } tariffs.stream() - .filter(r -> r.material().getId().equals(p.getMaterialId())) + .filter(r -> r.material().getId().equals(p.getMaterialId()) && r.countryId().equals(p.getCountryId())) .findFirst() .ifPresent(value -> premiseRepository.updateMaterial(Collections.singletonList( p.getId()), diff --git a/src/main/java/de/avatic/lcc/service/calculation/PremiseSearchStringAnalyzerService.java b/src/main/java/de/avatic/lcc/service/calculation/PremiseSearchStringAnalyzerService.java index d0392fd..8a00057 100644 --- a/src/main/java/de/avatic/lcc/service/calculation/PremiseSearchStringAnalyzerService.java +++ b/src/main/java/de/avatic/lcc/service/calculation/PremiseSearchStringAnalyzerService.java @@ -26,6 +26,7 @@ import java.util.regex.Pattern; @Service public class PremiseSearchStringAnalyzerService { + private static final String PART_NUMBER_REGEX_NUMBER_ONLY = "(\\d{4,11})"; private static final String PART_NUMBER_REGEX = "([a-zA-Z0-9][a-zA-Z0-9\\-]{4,11})"; private static final Pattern PART_NUMBER_PATTERN = Pattern.compile(PART_NUMBER_REGEX); private final MaterialRepository materialRepository; @@ -98,6 +99,8 @@ public class PremiseSearchStringAnalyzerService { Set partNumbers = new HashSet<>(); Matcher matcher = PART_NUMBER_PATTERN.matcher(search); + Matcher numbersOnlyMatcher = Pattern.compile(PART_NUMBER_REGEX_NUMBER_ONLY).matcher(search); + // Find all matches while (matcher.find()) { // Get the match from group 1 (inside the lookahead) @@ -105,6 +108,12 @@ public class PremiseSearchStringAnalyzerService { partNumbers.add(normalizePartNumber(partNumber)); } + while (numbersOnlyMatcher.find()) { + // Get the match from group 1 (inside the lookahead) + String partNumber = numbersOnlyMatcher.group(1); + partNumbers.add(normalizePartNumber(partNumber)); + } + return partNumbers; } diff --git a/src/main/java/de/avatic/lcc/service/transformer/users/UserTransformer.java b/src/main/java/de/avatic/lcc/service/transformer/users/UserTransformer.java index 3cf59c9..f7386b7 100644 --- a/src/main/java/de/avatic/lcc/service/transformer/users/UserTransformer.java +++ b/src/main/java/de/avatic/lcc/service/transformer/users/UserTransformer.java @@ -34,7 +34,7 @@ public class UserTransformer { return entity; } - private Group fromGroupDTO(String name) { + public Group fromGroupDTO(String name) { var group = new Group(); group.setName(name); return group; diff --git a/src/main/java/de/avatic/lcc/service/users/GroupService.java b/src/main/java/de/avatic/lcc/service/users/GroupService.java index 3e740cf..76ad3ba 100644 --- a/src/main/java/de/avatic/lcc/service/users/GroupService.java +++ b/src/main/java/de/avatic/lcc/service/users/GroupService.java @@ -63,6 +63,7 @@ public class GroupService { private GroupDTO toGroupDTO(Group group) { var dto = new GroupDTO(); + dto.setId(group.getId()); dto.setDescription( group.getDescription()); dto.setName(group.getName()); 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 28dad3e..399ff82 100644 --- a/src/main/java/de/avatic/lcc/service/users/UserService.java +++ b/src/main/java/de/avatic/lcc/service/users/UserService.java @@ -2,15 +2,21 @@ package de.avatic.lcc.service.users; import de.avatic.lcc.config.LccOidcUser; import de.avatic.lcc.dto.users.UserDTO; +import de.avatic.lcc.model.db.users.Group; 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.util.exception.badrequest.NotFoundException; +import de.avatic.lcc.util.exception.base.BadRequestException; +import jakarta.validation.constraints.NotNull; +import org.hibernate.validator.constraints.Length; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; -import java.util.Optional; +import java.util.*; +import java.util.stream.Collectors; /** * Service class responsible for handling business logic related to users. @@ -60,20 +66,43 @@ public class UserService { userAuthorityCacheService.invalidateUserAuthorities(userId); } - 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 oidcUser) { + public void appendGroups(String workdayId, String group) { + var optUser = userRepository.getByWorkdayId(workdayId); - return oidcUser.getAuthorities().stream().anyMatch(authority -> authority.getAuthority().equals("ROLE_SUPER")); + if (optUser.isEmpty()) { + throw new NotFoundException(NotFoundException.NotFoundType.USER, "workday id", workdayId); } + var user = optUser.get(); - return false; + var existingGroups = new TreeSet<>(Comparator.comparing(Group::getName)); + existingGroups.addAll(user.getGroups()); + existingGroups.add(userTransformer.fromGroupDTO(group)); + user.setGroups(new ArrayList<>(existingGroups)); + + userRepository.update(user); } -// public boolean canCalculate() { -// Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); -// } + public void deleteGroups(String workdayId, String groupToDelete) { + var optUser = userRepository.getByWorkdayId(workdayId); + + if (optUser.isEmpty()) throw new NotFoundException(NotFoundException.NotFoundType.USER, "workday id", workdayId); + var user = optUser.get(); + + List uniqueGroups = user.getGroups().stream() + .filter(group -> !group.getName().equals(groupToDelete)) + .collect(Collectors.toMap( + Group::getName, + group -> group, + (existing, replacement) -> existing, + LinkedHashMap::new + )) + .values() + .stream() + .toList(); + + user.setGroups(uniqueGroups); + userRepository.update(user); + } } diff --git a/src/main/java/de/avatic/lcc/util/exception/badrequest/NotFoundException.java b/src/main/java/de/avatic/lcc/util/exception/badrequest/NotFoundException.java index 3d0bc53..1e18adf 100644 --- a/src/main/java/de/avatic/lcc/util/exception/badrequest/NotFoundException.java +++ b/src/main/java/de/avatic/lcc/util/exception/badrequest/NotFoundException.java @@ -29,7 +29,8 @@ public class NotFoundException extends BadRequestException { COUNTRY_PROPERTY("Country property"), CONTAINER_RATE("Container rate"), EXPIRED_VALIDITY_PERIOD("Expired validity period"), - USER_NODE("User node"); + USER_NODE("User node"), + USER("User"); private final String identifier; diff --git a/src/main/resources/db/migration/V1__Create_schema.sql b/src/main/resources/db/migration/V1__Create_schema.sql index 1cda347..529fd00 100644 --- a/src/main/resources/db/migration/V1__Create_schema.sql +++ b/src/main/resources/db/migration/V1__Create_schema.sql @@ -300,6 +300,9 @@ CREATE TABLE IF NOT EXISTS material hs_code CHAR(11), name VARCHAR(500) NOT NULL, is_deprecated BOOLEAN NOT NULL DEFAULT FALSE, + INDEX idx_part_number (part_number), + INDEX idx_normalized_part_number (normalized_part_number), + INDEX idx_hs_code (hs_code), CONSTRAINT `uq_normalized_part_number` UNIQUE (`normalized_part_number`) ); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 0e40024..a122343 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -300,6 +300,9 @@ CREATE TABLE IF NOT EXISTS material hs_code CHAR(11), name VARCHAR(500) NOT NULL, is_deprecated BOOLEAN NOT NULL DEFAULT FALSE, + INDEX idx_part_number (part_number), + INDEX idx_normalized_part_number (normalized_part_number), + INDEX idx_hs_code (hs_code), CONSTRAINT `uq_normalized_part_number` UNIQUE (`normalized_part_number`) ); @@ -521,7 +524,9 @@ CREATE TABLE IF NOT EXISTS calculation_job FOREIGN KEY (user_id) REFERENCES sys_user (id), INDEX idx_premise_id (premise_id), INDEX idx_validity_period_id (validity_period_id), - INDEX idx_property_set_id (property_set_id) + INDEX idx_property_set_id (property_set_id), + INDEX idx_user_id (user_id) + );