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.
This commit is contained in:
parent
a381ac3299
commit
be7b94753e
18 changed files with 178 additions and 214 deletions
|
|
@ -3,20 +3,21 @@
|
|||
<div class="edit-user">
|
||||
<div>Workday ID</div>
|
||||
<div>
|
||||
<div class="text-container" :class="{disabled: !isNewUser}"><input :disabled="!isNewUser" class="input-field"
|
||||
v-model="user.workday_id"/></div>
|
||||
<div class="text-container" :class="{disabled: !isNewUser}"><input type="text" autocomplete="off" :disabled="!isNewUser" class="input-field"
|
||||
v-model="user.workday_id" maxlength="32"/>
|
||||
</div>
|
||||
</div>
|
||||
<div>Firstname</div>
|
||||
<div>
|
||||
<div class="text-container"><input class="input-field" v-model="user.firstname"/></div>
|
||||
<div class="text-container"><input type="text" autocomplete="off" class="input-field" v-model="user.firstname" maxlength="100"/></div>
|
||||
</div>
|
||||
<div>Lastname</div>
|
||||
<div>
|
||||
<div class="text-container"><input class="input-field" v-model="user.lastname"/></div>
|
||||
<div class="text-container"><input type="text" autocomplete="off" class="input-field" v-model="user.lastname" maxlength="100"/></div>
|
||||
</div>
|
||||
<div>E-Mail</div>
|
||||
<div>
|
||||
<div class="text-container"><input class="input-field" v-model="user.mail"/></div>
|
||||
<div class="text-container"><input autocomplete="off" type="email" class="input-field" v-model="user.mail" maxlength="254"/></div>
|
||||
</div>
|
||||
<div class="group-header">Groups</div>
|
||||
<div class="groups-container">
|
||||
|
|
@ -62,6 +63,11 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
validate(type) {
|
||||
if (type === "firstname") {
|
||||
firstName
|
||||
}
|
||||
},
|
||||
closeModal(save) {
|
||||
this.$emit("close", save);
|
||||
},
|
||||
|
|
@ -122,22 +128,13 @@ export default {
|
|||
|
||||
<style scoped>
|
||||
|
||||
.text-container.disabled {
|
||||
background-color: #f3f4f6;
|
||||
cursor: not-allowed;
|
||||
border-color: #f3f4f6;
|
||||
|
||||
.add-app-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 1.6rem;
|
||||
}
|
||||
|
||||
.text-container.disabled input {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
|
||||
.groups-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
|
||||
gap: 1.8rem;
|
||||
}
|
||||
|
||||
.edit-user-container {
|
||||
min-width: 100rem;
|
||||
|
|
@ -153,23 +150,17 @@ export default {
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.groups-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
|
||||
gap: 1.8rem;
|
||||
}
|
||||
|
||||
.group-header {
|
||||
align-self: start;
|
||||
|
||||
}
|
||||
|
||||
.text-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
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 0 auto;
|
||||
}
|
||||
|
||||
|
||||
.input-field {
|
||||
border: none;
|
||||
|
|
@ -181,12 +172,39 @@ export default {
|
|||
color: #002F54;
|
||||
width: 100%;
|
||||
min-width: 5rem;
|
||||
padding: 0.6rem 1.2rem;
|
||||
}
|
||||
|
||||
.add-app-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 1.6rem;
|
||||
|
||||
.text-container.disabled {
|
||||
background-color: #f3f4f6;
|
||||
cursor: not-allowed;
|
||||
border-color: #f3f4f6;
|
||||
}
|
||||
|
||||
.text-container.disabled input {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
|
||||
.text-container:hover:not(.disabled) {
|
||||
background: #EEF4FF;
|
||||
border: 0.2rem solid #8DB3FE;
|
||||
transform: scale(1.01);
|
||||
|
||||
}
|
||||
|
||||
.text-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: white;
|
||||
border-radius: 0.4rem;
|
||||
/* box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);*/
|
||||
border: 0.2rem solid #E3EDFF;
|
||||
transition: all 0.1s ease;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
|
|
@ -18,7 +18,6 @@
|
|||
|
||||
</div>
|
||||
</div>
|
||||
<Toast ref="toast"/>
|
||||
|
||||
<transition name="list-edit-container" tag="div">
|
||||
<transition-group name="list-edit" mode="out-in" class="edit-calculation-list-container" tag="div">
|
||||
|
|
@ -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"
|
||||
|
||||
>
|
||||
</component>
|
||||
<div class="modal-content-footer" v-if="showModalFooter">
|
||||
<basic-button :show-icon="false" @click="closeEditModalAction('accept')">OK</basic-button>
|
||||
<basic-button variant="secondary" :show-icon="false" @click="closeEditModalAction('cancel')">Cancel
|
||||
<div class="modal-content-footer" >
|
||||
<basic-button v-if="!modalCloseOnly" :show-icon="false" @click="closeEditModalAction('accept')">OK</basic-button>
|
||||
<basic-button variant="secondary" :show-icon="false" @click="closeEditModalAction('cancel')"> {{ modalCloseOnly ? "Close" : "Cancel" }}
|
||||
</basic-button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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<String, Object> keepalive(Authentication authentication) {
|
||||
|
||||
return Map.of(
|
||||
"status", "ok",
|
||||
"user", authentication.getName(),
|
||||
"timestamp", System.currentTimeMillis()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -78,8 +78,8 @@ public class PremiseController {
|
|||
public ResponseEntity<PremiseSearchResultDTO> 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Void> 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<Void> 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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
|
|
|
|||
|
|
@ -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<String> 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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return false;
|
||||
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<Group> 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);
|
||||
}
|
||||
|
||||
// public boolean canCalculate() {
|
||||
// Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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`)
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
);
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue