FRONTEND/BACKEND: Refactor destination editing and input components; add startCalculation method to PremiseEditStore, implement Properties and BulkUpload components, introduce case-insensitive deserialization for DimensionUnit and WeightUnit, improve UI handling and modularity across input fields, and revise DTO and repository logic for improved state handling and validation.
This commit is contained in:
parent
f5c4e1159f
commit
45742d731d
41 changed files with 1775 additions and 177 deletions
|
|
@ -3,9 +3,10 @@
|
|||
<button
|
||||
ref="trigger"
|
||||
class="dropdown-trigger"
|
||||
:class="{ 'dropdown-trigger--open': isOpen }"
|
||||
:class="{ 'dropdown-trigger--open': isOpen}"
|
||||
@click="toggleDropdown"
|
||||
@keydown="handleTriggerKeydown"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<span class="dropdown-trigger-text">
|
||||
{{ selectedOption ? selectedOption[displayKey] : placeholder }}
|
||||
|
|
@ -94,7 +95,7 @@ export default {
|
|||
return {
|
||||
isOpen: false,
|
||||
focusedIndex: -1,
|
||||
labelId: `dropdown-${Math.random().toString(36).substr(2, 9)}`
|
||||
labelId: `dropdown-${Math.random().toString(36).substring(2, 9)}`
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -327,14 +328,26 @@ export default {
|
|||
|
||||
/* Disabled state */
|
||||
.dropdown-trigger:disabled {
|
||||
background-color: #f7fafc;
|
||||
color: #a0aec0;
|
||||
background: white;
|
||||
cursor: not-allowed;
|
||||
border-color: #e2e8f0;
|
||||
border: 0.2rem solid rgba(227, 237, 255, 0.5);
|
||||
color: rgba(0, 47, 84, 0.3);
|
||||
}
|
||||
|
||||
.dropdown-trigger:disabled:hover {
|
||||
border-color: #e2e8f0;
|
||||
border: 0.2rem solid rgba(227, 237, 255, 0.5);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
|
||||
/* Add this CSS rule to your existing styles */
|
||||
.dropdown-trigger:disabled .dropdown-trigger-text {
|
||||
color: rgba(0, 47, 84, 0.5);
|
||||
}
|
||||
|
||||
/* You might also want to style the icon when disabled */
|
||||
.dropdown-trigger:disabled .dropdown-trigger-icon {
|
||||
color: rgba(113, 128, 150, 0.5);
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="input-field-container">
|
||||
<div class="text-container">
|
||||
<input class="input-field"
|
||||
v-model="modelValue"
|
||||
:placeholder="placeholder"
|
||||
|
|
@ -41,7 +41,7 @@ export default {
|
|||
color: #002F54;
|
||||
}
|
||||
|
||||
.input-field-container {
|
||||
.text-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: white;
|
||||
|
|
@ -53,7 +53,7 @@ export default {
|
|||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.input-field-container:hover {
|
||||
.text-container:hover {
|
||||
background: #EEF4FF;
|
||||
border: 0.2rem solid #8DB3FE;
|
||||
/*transform: translateY(2px);*/
|
||||
|
|
|
|||
125
src/frontend/src/components/UI/ToogleSwitch.vue
Normal file
125
src/frontend/src/components/UI/ToogleSwitch.vue
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
<template>
|
||||
<div class="toggle-switch-wrapper">
|
||||
<label class="toggle-switch" :class="{ 'disabled': disabled }">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="modelValue"
|
||||
:disabled="disabled"
|
||||
@change="handleToggle"
|
||||
class="toggle-input"
|
||||
/>
|
||||
<span class="toggle-slider" :class="{ 'active': modelValue }">
|
||||
<span class="toggle-knob" :class="{ 'active': modelValue }"></span>
|
||||
</span>
|
||||
<span v-if="label" class="toggle-label">{{ label }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ToggleSwitch',
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'change'],
|
||||
methods: {
|
||||
handleToggle(event) {
|
||||
if (this.disabled) return;
|
||||
|
||||
const newValue = event.target.checked;
|
||||
this.$emit('update:modelValue', newValue);
|
||||
this.$emit('change', newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.toggle-switch-wrapper {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.toggle-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
gap: 1.2rem;
|
||||
}
|
||||
|
||||
.toggle-switch.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.toggle-input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.toggle-slider {
|
||||
position: relative;
|
||||
width: 4.8rem;
|
||||
height: 2.4rem;
|
||||
background-color: rgba(107, 134, 156, 0.1);
|
||||
border-radius: 1.4rem;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: inset 0 0.2rem 0.4rem rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.toggle-slider.active {
|
||||
background-color: #5AF0B4;
|
||||
}
|
||||
|
||||
.toggle-knob {
|
||||
position: absolute;
|
||||
top: 0.4rem;
|
||||
left: 0.4rem;
|
||||
width: 1.6rem;
|
||||
height: 1.6rem;
|
||||
background-color: #ffffff;
|
||||
border-radius: 50%;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 0.2rem 0.4rem rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.toggle-knob.active {
|
||||
transform: translateX(2.4rem);
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.toggle-label {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
/* Hover-Effekte */
|
||||
.toggle-switch:not(.disabled):hover .toggle-slider {
|
||||
box-shadow: inset 0 0.2rem 0.4rem rgba(0, 0, 0, 0.15), 0 0 0 0.2rem rgba(0, 47, 84, 0.1);
|
||||
}
|
||||
|
||||
.toggle-switch:not(.disabled):hover .toggle-slider.active {
|
||||
box-shadow: inset 0 0.2rem 0.4rem rgba(0, 0, 0, 0.15), 0 0 0 0.2rem rgba(0, 47, 84, 0.15);
|
||||
}
|
||||
|
||||
/* Animation beim Laden */
|
||||
.toggle-slider,
|
||||
.toggle-knob {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -13,7 +13,8 @@
|
|||
HS Code:
|
||||
{{ premise.material.hs_code }}
|
||||
</div>
|
||||
<div class="edit-calculation-cell-line edit-calculation-cell-subline" v-if="premise.tariff_rate && premise.tariff_rate > 0">
|
||||
<div class="edit-calculation-cell-line edit-calculation-cell-subline"
|
||||
v-if="premise.tariff_rate && premise.tariff_rate > 0">
|
||||
Tariff rate:
|
||||
{{ toPercent(premise.tariff_rate) }} %
|
||||
</div>
|
||||
|
|
@ -21,7 +22,9 @@
|
|||
<div class="edit-calculation-cell--price" :class="copyModeClass" v-if="showPrice"
|
||||
@click="action('price')">
|
||||
<div class="edit-calculation-cell-line">{{ premise.material_cost }} EUR</div>
|
||||
<div class="edit-calculation-cell-line edit-calculation-cell-subline">Oversea share: {{ toPercent(premise.oversea_share) }} %</div>
|
||||
<div class="edit-calculation-cell-line edit-calculation-cell-subline">Oversea share:
|
||||
{{ toPercent(premise.oversea_share) }} %
|
||||
</div>
|
||||
<div class="edit-calculation-cell-line edit-calculation-cell-subline" v-if="premise.is_fca_enabled">
|
||||
<basic-badge icon="plus" variant="primary">FCA FEE</basic-badge>
|
||||
</div>
|
||||
|
|
@ -62,7 +65,7 @@
|
|||
<!-- </div>-->
|
||||
<div class="calculation-list-supplier-data">
|
||||
<div class="edit-calculation-cell-line">{{ premise.supplier.name }}</div>
|
||||
<!-- <div class="edit-calculation-cell-subline"> {{ premise.supplier.address }}</div>-->
|
||||
<div class="edit-calculation-cell-subline"> {{ premise.supplier.address }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -72,9 +75,12 @@
|
|||
<span class="number-circle"> {{ destinationsCount }} </span> Destinations
|
||||
</div>
|
||||
<div class="edit-calculation-cell-subline" v-for="name in destinationNames"> {{ name }}</div>
|
||||
<div class="edit-calculation-cell-subline" v-if="showDestinationIncomplete">
|
||||
<basic-badge variant="exception" icon="warning">INCOMPLETE</basic-badge>
|
||||
</div>
|
||||
</div>
|
||||
<div class="edit-calculation-empty" v-else-if="showMassEdit">
|
||||
<spinner> </spinner>
|
||||
<spinner></spinner>
|
||||
</div>
|
||||
<div class="edit-calculation-empty" :class="copyModeClass" v-else
|
||||
@click="action('destinations')">
|
||||
|
|
@ -140,7 +146,7 @@ export default {
|
|||
return this.premise.destinations.map(d => d.destination_node.name).join(', ');
|
||||
},
|
||||
destinationNames() {
|
||||
const spliceCnt = ((this.premise.destinations.length === 4) ? 4 : 3);
|
||||
const spliceCnt = ((this.premise.destinations.length === 4) ? 4 : 3) - this.showDestinationIncomplete;
|
||||
const names = this.premise.destinations.map(d => d.destination_node.name).slice(0, spliceCnt);
|
||||
if (this.premise.destinations.length > 4) {
|
||||
names.push('and more ...');
|
||||
|
|
@ -148,6 +154,9 @@ export default {
|
|||
|
||||
return names;
|
||||
},
|
||||
showDestinationIncomplete() {
|
||||
return this.premise.destinations.some(p => ((p.annual_amount ?? null) === null) || p.annual_amount === 0 || p.routes?.every(r => !r.is_selected))
|
||||
},
|
||||
showDestinations() {
|
||||
return (this.destinationsCount > 0);
|
||||
},
|
||||
|
|
|
|||
15
src/frontend/src/components/layout/config/BulkUpload.vue
Normal file
15
src/frontend/src/components/layout/config/BulkUpload.vue
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<template>
|
||||
<h3>Bulk Operations</h3>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: "BulkUpload"
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<template>
|
||||
<h3>Country properties</h3>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import {mapStores} from "pinia";
|
||||
import {usePropertiesStore} from "@/store/properties.js";
|
||||
import {usePremiseEditStore} from "@/store/premiseEdit.js";
|
||||
|
||||
export default {
|
||||
name: "CountryProperties",
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
151
src/frontend/src/components/layout/config/Properties.vue
Normal file
151
src/frontend/src/components/layout/config/Properties.vue
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
<template>
|
||||
<div class="properties-container">
|
||||
|
||||
<div v-if="!loading" class="period-select-container"><span class="period-select-caption">Property set:</span>
|
||||
<dropdown :options="periods"
|
||||
emptyText="No property set available"
|
||||
class="period-select"
|
||||
placeholder="Select a property set"
|
||||
v-model="selectedPeriod"
|
||||
|
||||
></dropdown>
|
||||
<tooltip position="left" text="Invalidate the selected property set">
|
||||
<icon-button icon="trash" @click="deletePeriod" :disabled="disableDeleteButton"></icon-button>
|
||||
</tooltip>
|
||||
<modal-dialog title="Do you really want to invalidate this property set?" dismiss-text="No" accept-text="Yes"
|
||||
:state="modalDialogDeleteState"
|
||||
message="If you invalidate this property set, this will also invalidate all calculations done with this property set. This cannot be undone!"
|
||||
@click="deleteModalClick"
|
||||
>
|
||||
</modal-dialog>
|
||||
</div>
|
||||
|
||||
<div v-if="!loading" class="properties-list">
|
||||
<property v-for="property in properties" :key="property.external_mapping_id" :property="property"
|
||||
:disabled="!isValidPeriodActive" @save="saveProperty"></property>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import {mapStores} from "pinia";
|
||||
import {usePropertiesStore} from "@/store/properties.js";
|
||||
import Property from "@/components/layout/config/Property.vue";
|
||||
import Dropdown from "@/components/UI/Dropdown.vue";
|
||||
import BasicButton from "@/components/UI/BasicButton.vue";
|
||||
import IconButton from "@/components/UI/IconButton.vue";
|
||||
import Tooltip from "@/components/UI/Tooltip.vue";
|
||||
import modalDialog from "@/components/UI/ModalDialog.vue";
|
||||
import ModalDialog from "@/components/UI/ModalDialog.vue";
|
||||
import NotificationBar from "@/components/UI/NotificationBar.vue";
|
||||
import {usePropertySetsStore} from "@/store/propertySets.js";
|
||||
|
||||
export default {
|
||||
name: "Properties",
|
||||
components: {NotificationBar, ModalDialog, Tooltip, IconButton, BasicButton, Dropdown, Property},
|
||||
data() {
|
||||
return {
|
||||
modalDialogDeleteState: false,
|
||||
selectedPeriodId: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(usePropertiesStore, usePropertySetsStore),
|
||||
loading() {
|
||||
return this.propertiesStore.isLoading;
|
||||
},
|
||||
isValidPeriodActive() {
|
||||
const state = this.propertySetsStore.getPeriodState(this.selectedPeriod);
|
||||
return state === "VALID" || state === "DRAFT";
|
||||
},
|
||||
disableDeleteButton() {
|
||||
const state = this.propertySetsStore.getPeriodState(this.selectedPeriod);
|
||||
return state === "VALID" || state === "INVALID" || state === "DRAFT";
|
||||
},
|
||||
selectedPeriod: {
|
||||
get() {
|
||||
return this.selectedPeriodId === null ? this.propertySetsStore.getCurrentPeriodId : this.selectedPeriodId;
|
||||
},
|
||||
set(value) {
|
||||
console.log(value)
|
||||
this.selectedPeriodId = value;
|
||||
this.propertiesStore.loadProperties(value);
|
||||
}
|
||||
},
|
||||
properties() {
|
||||
return this.propertiesStore.getProperties;
|
||||
},
|
||||
periods() {
|
||||
const periods = [];
|
||||
|
||||
const ps = this.propertySetsStore.getPeriods;
|
||||
const current = this.propertySetsStore.getCurrentPeriodId;
|
||||
|
||||
if ((ps ?? null) === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const p of ps) {
|
||||
const value = (p.state === "DRAFT" || p.state === "VALID") ? "CURRENT" : `${this.buildDate(p.start_date)} - ${this.buildDate(p.end_date)} ${p.state === "INVALID" ? "(INVALID)" : ""}`;
|
||||
const period = {id: p.id, value: value};
|
||||
|
||||
console.log(p, p.state !== "VALID" , p.id === current, period)
|
||||
|
||||
if (p.state !== "VALID" || p.id === current)
|
||||
periods.push(period);
|
||||
}
|
||||
|
||||
return periods;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.propertiesStore.reload();
|
||||
},
|
||||
methods: {
|
||||
|
||||
saveProperty(property) {
|
||||
this.propertiesStore.setProperty(property);
|
||||
},
|
||||
buildDate(date) {
|
||||
return `${date[0]}-${date[1].toString().padStart(2, '0')}-${date[2].toString().padStart(2, '0')} ${date[3].toString().padStart(2, '0')}:${date[4].toString().padStart(2, '0')}:${date[5].toString().padStart(2, '0')}`
|
||||
},
|
||||
deletePeriod() {
|
||||
if (!this.disableDeleteButton) {
|
||||
this.modalDialogDeleteState = true;
|
||||
}
|
||||
},
|
||||
deleteModalClick(action) {
|
||||
this.modalDialogDeleteState = false;
|
||||
|
||||
if (action === 'accept')
|
||||
this.propertySetsStore.invalidate(this.selectedPeriodId);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
|
||||
.period-select-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
gap: 1.6rem;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.period-select {
|
||||
flex: 0 1 40rem
|
||||
|
||||
}
|
||||
|
||||
.period-select-caption {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
</style>
|
||||
317
src/frontend/src/components/layout/config/Property.vue
Normal file
317
src/frontend/src/components/layout/config/Property.vue
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
<template>
|
||||
<div class="property-container">
|
||||
|
||||
<div class="caption-column">
|
||||
<div class="caption-column-id">{{ property.name }}:</div>
|
||||
</div>
|
||||
|
||||
<div class="input-column">
|
||||
|
||||
<div v-if="showTextField" :class="textContainerClasses">
|
||||
<input :disabled="disabled" @blur="validate('text', $event)" :class="inputFieldClasses" :value="currentValue"
|
||||
ref="textInputField"
|
||||
autocomplete="off"/>
|
||||
</div>
|
||||
|
||||
<div v-if="showToggleSwitch" class="boolean-container">
|
||||
<toggle-switch v-model="currentValue" @change="validate('toggle', $event)" :disabled="disabled"></toggle-switch>
|
||||
</div>
|
||||
|
||||
<div v-if="showDropDown" class="dropdown-container">
|
||||
<dropdown :disabled="disabled" :options="options" v-model:model-value="currentValue"
|
||||
@change="validate('dropdown', $event)"
|
||||
></dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="reset-button-container">
|
||||
<tooltip text="reset changes" :position="'left'" v-if="showResetButton">
|
||||
<icon-button icon="ArrowCounterClockwise" @click="resetProperty"></icon-button>
|
||||
</tooltip>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import InputField from "@/components/UI/InputField.vue";
|
||||
import {PhGear} from "@phosphor-icons/vue";
|
||||
import ToggleSwitch from "@/components/UI/ToogleSwitch.vue";
|
||||
import Dropdown from "@/components/UI/Dropdown.vue";
|
||||
import IconButton from "@/components/UI/IconButton.vue";
|
||||
import Tooltip from "@/components/UI/Tooltip.vue";
|
||||
import {parseNumberFromString} from "@/common.js";
|
||||
|
||||
export default {
|
||||
name: "Property",
|
||||
components: {Tooltip, IconButton, Dropdown, ToggleSwitch, PhGear, InputField},
|
||||
props: {
|
||||
property: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showTextField() {
|
||||
return !(this.showDropDown || this.showToggleSwitch);
|
||||
},
|
||||
showDropDown() {
|
||||
return this.property.data_type === 'ENUMERATION';
|
||||
},
|
||||
showToggleSwitch() {
|
||||
return this.property.data_type === 'BOOLEAN';
|
||||
},
|
||||
showResetButton() {
|
||||
return this.property.current_value !== this.property.draft_value && this.property.draft_value !== null;
|
||||
},
|
||||
currentValue: {
|
||||
get() {
|
||||
if (this.property.data_type === 'INT')
|
||||
return this.value.toFixed();
|
||||
if (this.property.data_type === 'PERCENTAGE')
|
||||
return `${(this.value * 100).toFixed(2)}%`;
|
||||
if (this.property.data_type === 'CURRENCY')
|
||||
return `${(this.value).toFixed(2)}€`;
|
||||
|
||||
return this.value;
|
||||
},
|
||||
set(value) {
|
||||
|
||||
}
|
||||
},
|
||||
textContainerClasses() {
|
||||
return this.disabled ? 'text-container--disabled' : 'text-container';
|
||||
},
|
||||
inputFieldClasses() {
|
||||
return this.disabled ? 'input-field--disabled' : 'input-field';
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
options: {},
|
||||
value: null,
|
||||
parsedText: null,
|
||||
validationRule: null,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.property === null) return;
|
||||
this.initialProcessing();
|
||||
},
|
||||
methods: {
|
||||
validate(type, event) {
|
||||
let emitEvent = false;
|
||||
|
||||
if (type === 'text') {
|
||||
|
||||
if (this.property.data_type === 'CURRENCY' || this.property.data_type === 'PERCENTAGE' || this.property.data_type === 'INT') {
|
||||
const parsed = parseNumberFromString(event.target.value, (this.property.data_type === 'INT') ? 0 : 2) * ((this.property.data_type === 'PERCENTAGE') ? 0.01 : 1);
|
||||
emitEvent = (parsed !== this.value);
|
||||
this.value = parsed;
|
||||
this.updateInputValue();
|
||||
} else if (this.property.data_type === 'TEXT') {
|
||||
emitEvent = (event.target.value !== this.value);
|
||||
this.value = event.target.value;
|
||||
}
|
||||
|
||||
} else if (type === 'toggle') {
|
||||
emitEvent = (event !== this.value);
|
||||
this.value = event;
|
||||
} else if (type === 'dropdown') {
|
||||
emitEvent = (event.value !== this.value);
|
||||
this.value = event.value;
|
||||
}
|
||||
|
||||
if (emitEvent)
|
||||
this.$emit('save', {id: this.property.external_mapping_id, value: this.value, reset: false});
|
||||
|
||||
|
||||
},
|
||||
updateInputValue() {
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs["textInputField"] && this.$refs["textInputField"].value !== this.currentValue) {
|
||||
this.$refs["textInputField"].value = this.currentValue;
|
||||
}
|
||||
});
|
||||
},
|
||||
getEnum(rule) {
|
||||
const options = [];
|
||||
const enumValues = rule['ENUM'];
|
||||
|
||||
for (const value of enumValues) {
|
||||
const option = {
|
||||
id: value,
|
||||
value: value,
|
||||
}
|
||||
|
||||
options.push(option);
|
||||
}
|
||||
|
||||
return options;
|
||||
},
|
||||
resetProperty() {
|
||||
this.property.draft_value = null;
|
||||
this.initialProcessing();
|
||||
|
||||
this.$emit('save', {id: this.property.external_mapping_id, value: this.value, reset: true});
|
||||
|
||||
},
|
||||
initialProcessing() {
|
||||
this.validationRule = JSON.parse(this.property.validation_rule);
|
||||
this.value = this.property.draft_value === null ? this.property.current_value : this.property.draft_value;
|
||||
|
||||
if (this.property.data_type === 'ENUMERATION') {
|
||||
this.options = this.getEnum(this.validationRule);
|
||||
}
|
||||
|
||||
if (this.property.data_type === 'BOOLEAN') {
|
||||
this.value = this.value === 'true';
|
||||
}
|
||||
|
||||
if (this.property.data_type === 'INT') {
|
||||
this.value = parseNumberFromString(this.value, 0);
|
||||
}
|
||||
|
||||
if (this.property.data_type === 'PERCENTAGE') {
|
||||
this.value = parseNumberFromString(this.value, 4);
|
||||
}
|
||||
|
||||
if (this.property.data_type === 'CURRENCY') {
|
||||
this.value = parseNumberFromString(this.value, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.property-container {
|
||||
display: grid;
|
||||
grid-template-columns: 3fr 1fr 0.5fr;
|
||||
align-items: center;
|
||||
gap: 2.4rem;
|
||||
|
||||
height: 8rem;
|
||||
}
|
||||
|
||||
.caption-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-weight: 300;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.caption-column-id {
|
||||
font-weight: 500;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.caption-column-name {
|
||||
font-weight: 400;
|
||||
font-size: 1.4rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.input-column {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 300;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.text-container--disabled {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: white;
|
||||
border-radius: 0.4rem;
|
||||
padding: 0.6rem 1.2rem;
|
||||
border: 0.2rem solid rgba(227, 237, 255, 0.5);
|
||||
color: rgba(0, 47, 84, 0.3);
|
||||
transition: all 0.1s ease;
|
||||
width: 100%;
|
||||
max-width: 30rem;
|
||||
min-width: 20rem;
|
||||
}
|
||||
|
||||
.text-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: white;
|
||||
border-radius: 0.4rem;
|
||||
padding: 0.6rem 1.2rem;
|
||||
border: 0.2rem solid #E3EDFF;
|
||||
transition: all 0.1s ease;
|
||||
width: 100%;
|
||||
max-width: 30rem;
|
||||
min-width: 20rem;
|
||||
}
|
||||
|
||||
.text-container:hover {
|
||||
background: #EEF4FF;
|
||||
border: 0.2rem solid #8DB3FE;
|
||||
transform: scale(1.01);
|
||||
}
|
||||
|
||||
.boolean-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
max-width: 30rem;
|
||||
min-width: 20rem;
|
||||
}
|
||||
|
||||
.dropdown-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
max-width: 30rem;
|
||||
min-width: 20rem;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
border: none;
|
||||
outline: none;
|
||||
background: none;
|
||||
resize: none;
|
||||
font-family: inherit;
|
||||
font-size: 1.4rem;
|
||||
color: #002F54;
|
||||
width: 100%;
|
||||
min-width: 5rem;
|
||||
max-width: 100rem;
|
||||
}
|
||||
|
||||
|
||||
.input-field--disabled {
|
||||
border: none;
|
||||
outline: none;
|
||||
background: none;
|
||||
resize: none;
|
||||
font-family: inherit;
|
||||
font-size: 1.4rem;
|
||||
color: rgba(0, 47, 84, 0.5);
|
||||
width: 100%;
|
||||
min-width: 5rem;
|
||||
max-width: 100rem;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
|
||||
.reset-button-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.1s ease;
|
||||
}
|
||||
|
||||
</style>
|
||||
110
src/frontend/src/components/layout/config/StagedChanges.vue
Normal file
110
src/frontend/src/components/layout/config/StagedChanges.vue
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
<template>
|
||||
<div>
|
||||
<transition name="fade">
|
||||
<div v-if="stagedChanges" class="staged-changes-container">
|
||||
<div class="staged-changes-info">
|
||||
<ph-warning size="18px"></ph-warning>
|
||||
There are changes to system properties or country properties. Press save icon to apply them.
|
||||
</div>
|
||||
<div class="staged-changes-save">
|
||||
<icon-button icon="floppy-disk" @click="applyChanges" variant="blue"></icon-button>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<modal-dialog title="Do you really want to apply the current changes?" dismiss-text="No" accept-text="Yes"
|
||||
:state="modalDialogStagedChangesState"
|
||||
message='As soon as you change the system properties and country properties, new calculations are no longer comparable with previous calculations.
|
||||
Therefore, a new "Property set" is created. Only calculations that were created with the same "Property set" can be compared within reporting. Would you like to continue?'
|
||||
@click="applyChangesModalClick"
|
||||
>
|
||||
</modal-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import IconButton from "@/components/UI/IconButton.vue";
|
||||
import ModalDialog from "@/components/UI/ModalDialog.vue";
|
||||
import {mapStores} from "pinia";
|
||||
import {useStageStore} from "@/store/stage.js";
|
||||
import {usePropertiesStore} from "@/store/properties.js";
|
||||
|
||||
export default {
|
||||
name: "StagedChanges",
|
||||
components: {ModalDialog, IconButton},
|
||||
data() {
|
||||
return {
|
||||
modalDialogStagedChangesState: false,
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
... mapStores(useStageStore, usePropertiesStore),
|
||||
stagedChanges() {
|
||||
return this.stageStore.hasStagedChanges;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
applyChanges() {
|
||||
this.modalDialogStagedChangesState = true;
|
||||
|
||||
},
|
||||
async applyChangesModalClick(action) {
|
||||
this.modalDialogStagedChangesState = false;
|
||||
if (action === 'accept') {
|
||||
await this.stageStore.applyChanges();
|
||||
await this.propertiesStore.reload();
|
||||
await this.countryPropertiesStore.reload();
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.stageStore.checkStagedChanges();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.staged-changes-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 1.4rem;
|
||||
gap: 1.6rem;
|
||||
background-color: #5AF0B4;
|
||||
color: #002F54;
|
||||
border-radius: 0.8rem;
|
||||
padding: 1.6rem;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
.staged-changes-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.6rem;
|
||||
}
|
||||
|
||||
.staged-changes-save {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.6rem;
|
||||
}
|
||||
|
||||
/* Fade transition styles */
|
||||
.fade-enter-active {
|
||||
transition: opacity 0.3s ease-in;
|
||||
}
|
||||
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s ease-out;
|
||||
}
|
||||
|
||||
.fade-enter-from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -27,14 +27,14 @@
|
|||
|
||||
<div class="input-column">
|
||||
<div class="hs-code-container">
|
||||
<autosuggest-searchbar :fetch-suggestions="fetchHsCode" :initial-value="hsCode"
|
||||
<autosuggest-searchbar :activate-watcher="true" :fetch-suggestions="fetchHsCode" :initial-value="hsCode"
|
||||
placeholder="Find hs code" no-results-text="Not found."></autosuggest-searchbar>
|
||||
<icon-button icon="ArrowCounterClockwise"></icon-button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="caption-column">Tariff rate [%]</div>
|
||||
<div class="input-field-container input-field-tariffrate">
|
||||
<div class="text-container input-field-tariffrate">
|
||||
<input ref="tariffRateInput" :value="tariffRatePercent" @blur="validateTariffRate"
|
||||
class="input-field"
|
||||
autocomplete="off"/>
|
||||
|
|
@ -67,7 +67,7 @@ export default {
|
|||
SelectMaterial,
|
||||
Modal, PhArrowCounterClockwise, ModalDialog, AutosuggestSearchbar, InputField, Flag, IconButton
|
||||
},
|
||||
emits: ["update:tariffRate", "updateMaterial", "update:partNumber", "update:hsCode", "save"],
|
||||
emits: ["update:tariffRate", "updateMaterial", "update:partNumber", "update:hsCode", "save", "close"],
|
||||
props: {
|
||||
description: {
|
||||
type: String,
|
||||
|
|
@ -85,6 +85,10 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
openSelectDirect: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useMaterialStore, useCustomsStore),
|
||||
|
|
@ -97,6 +101,9 @@ export default {
|
|||
modalSelectMaterial: false,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.modalSelectMaterial = this.openSelectDirect;
|
||||
},
|
||||
methods: {
|
||||
focusLost(event) {
|
||||
if (!this.$el.contains(event.relatedTarget)) {
|
||||
|
|
@ -105,12 +112,18 @@ export default {
|
|||
},
|
||||
closeEditModal() {
|
||||
this.modalSelectMaterial = false;
|
||||
if (this.openSelectDirect) {
|
||||
this.$emit('close');
|
||||
}
|
||||
},
|
||||
modalEditClick(data) {
|
||||
this.closeEditModal();
|
||||
this.modalSelectMaterial = false;
|
||||
|
||||
if (data.action === 'accept') {
|
||||
this.selectedMaterial = data.material;
|
||||
this.$emit('updateMaterial', data.material.id, data.updateMasterData ? 'updateMasterData' : 'keepMasterData');
|
||||
} else if (this.openSelectDirect) {
|
||||
this.$emit('close');
|
||||
}
|
||||
},
|
||||
updateInputValue(inputRef, formattedValue) {
|
||||
|
|
@ -182,7 +195,7 @@ export default {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.input-field-container {
|
||||
.text-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: white;
|
||||
|
|
@ -194,7 +207,7 @@ export default {
|
|||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.input-field-container:hover {
|
||||
.text-container:hover {
|
||||
background: #EEF4FF;
|
||||
border: 0.2rem solid #8DB3FE;
|
||||
/*transform: translateY(2px);*/
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<div class="container" @focusout="focusLost">
|
||||
<div class="caption-column">Length</div>
|
||||
<div class="input-column">
|
||||
<div class="input-field-container">
|
||||
<div class="text-container">
|
||||
<input ref="lengthInput" :value="huLength" @blur="validateDimension('length', $event)" class="input-field"
|
||||
autocomplete="off"/>
|
||||
</div>
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
<div class="caption-column">Width</div>
|
||||
<div class="input-column">
|
||||
<div class="input-field-container">
|
||||
<div class="text-container">
|
||||
<input ref="widthInput" :value="huWidth" @blur="validateDimension('width', $event)" class="input-field"
|
||||
autocomplete="off"/>
|
||||
</div>
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<div class="caption-column">Height</div>
|
||||
<div class="input-column">
|
||||
<div class="input-field-container">
|
||||
<div class="text-container">
|
||||
<input ref="heightInput" :value="huHeight" @blur="validateDimension('height', $event)" class="input-field"
|
||||
autocomplete="off"/>
|
||||
</div>
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
|
||||
<div class="caption-column">Weight</div>
|
||||
<div class="input-column">
|
||||
<div class="input-field-container">
|
||||
<div class="text-container">
|
||||
<input ref="weightInput" :value="huWeight" @blur="validateWeight('weight', $event)" class="input-field"
|
||||
autocomplete="off"/>
|
||||
</div>
|
||||
|
|
@ -49,7 +49,7 @@
|
|||
|
||||
<div class="caption-column">Pieces per HU</div>
|
||||
<div class="input-column">
|
||||
<div class="input-field-container">
|
||||
<div class="text-container">
|
||||
<input ref="unitCountInput" :value="huUnitCount" @blur="validateCount" class="input-field"
|
||||
autocomplete="off"/>
|
||||
</div>
|
||||
|
|
@ -292,7 +292,7 @@ export default {
|
|||
color: #6b7280;
|
||||
}
|
||||
|
||||
.input-field-container {
|
||||
.text-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: white;
|
||||
|
|
@ -305,7 +305,7 @@ export default {
|
|||
|
||||
}
|
||||
|
||||
.input-field-container:hover {
|
||||
.text-container:hover {
|
||||
background: #EEF4FF;
|
||||
border: 0.2rem solid #8DB3FE;
|
||||
/*transform: translateY(2px);*/
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@
|
|||
<div class="container" @focusout="focusLost">
|
||||
<div class="caption-column">MEK_A [EUR]</div>
|
||||
<div class="input-column">
|
||||
<div class="input-field-container">
|
||||
<div class="text-container">
|
||||
<input :value="priceFormatted" @blur="validatePrice" class="input-field"
|
||||
autocomplete="off"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="caption-column">Oversea share [%]</div>
|
||||
<div class="input-column">
|
||||
<div class="input-field-container">
|
||||
<div class="text-container">
|
||||
<input :value="overSeaSharePercent" @blur="validateOverSeaShare" class="input-field"
|
||||
autocomplete="off"/>
|
||||
</div>
|
||||
|
|
@ -127,7 +127,7 @@ export default {
|
|||
min-width: 5rem;
|
||||
}
|
||||
|
||||
.input-field-container {
|
||||
.text-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: white;
|
||||
|
|
@ -139,7 +139,7 @@ export default {
|
|||
flex: 1 1 fit-content(80rem);
|
||||
}
|
||||
|
||||
.input-field-container:hover {
|
||||
.text-container:hover {
|
||||
background: #EEF4FF;
|
||||
border: 0.2rem solid #8DB3FE;
|
||||
/*transform: translateY(2px);*/
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
</div>
|
||||
<div class="footer">
|
||||
<modal :state="selectSupplierModalState" @close="closeEditModal">
|
||||
<select-node @close="modalDialogClose"></select-node>
|
||||
<select-node @update-supplier="modalDialogClose"></select-node>
|
||||
</modal>
|
||||
<icon-button icon="plus" @click="openModal"></icon-button>
|
||||
<icon-button icon="pencil-simple" @click="openModal"></icon-button>
|
||||
|
|
@ -65,6 +65,10 @@ export default {
|
|||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
openSelectDirect: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -79,6 +83,9 @@ export default {
|
|||
selectSupplierModalState: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.selectSupplierModalState = this.openSelectDirect;
|
||||
},
|
||||
methods: {
|
||||
closeEditModal() {
|
||||
this.selectSupplierModalState = false;
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ export default {
|
|||
min-height: 0; /* Critical: allows flex child to shrink below content size */
|
||||
}
|
||||
|
||||
|
||||
|
||||
.destination-edit-modal-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
|||
|
|
@ -13,21 +13,21 @@
|
|||
<div class="destination-edit-handling-cost-container" v-show="inputFieldsActive">
|
||||
<div class="destination-edit-column-caption">Repackaging cost [EUR]</div>
|
||||
<div class="destination-edit-column-data">
|
||||
<div class="input-field-container">
|
||||
<div class="text-container">
|
||||
<input :value="repackaging" @blur="validate('repackaging', $event)" class="input-field"
|
||||
autocomplete="off"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="destination-edit-column-caption">Handling cost [EUR]</div>
|
||||
<div class="destination-edit-column-data">
|
||||
<div class="input-field-container">
|
||||
<div class="text-container">
|
||||
<input :value="handling" @blur="validate('handling', $event)" class="input-field"
|
||||
autocomplete="off"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="destination-edit-column-caption">Disposal cost [EUR]</div>
|
||||
<div class="destination-edit-column-data">
|
||||
<div class="input-field-container">
|
||||
<div class="text-container">
|
||||
<input :value="disposal" @blur="validate('disposal', $event)" class="input-field"
|
||||
autocomplete="off"/>
|
||||
</div>
|
||||
|
|
@ -161,7 +161,7 @@ export default {
|
|||
min-width: 5rem;
|
||||
}
|
||||
|
||||
.input-field-container {
|
||||
.text-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: white;
|
||||
|
|
@ -172,7 +172,7 @@ export default {
|
|||
flex: 1 1 fit-content(80rem);
|
||||
}
|
||||
|
||||
.input-field-container:hover {
|
||||
.text-container:hover {
|
||||
background: #EEF4FF;
|
||||
border: 0.2rem solid #8DB3FE;
|
||||
transform: scale(1.01);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
<tooltip :text="tooltipAnnualAmount" position="right">Annual quantity</tooltip>
|
||||
</div>
|
||||
<div class="destination-edit-column-data">
|
||||
<div class="input-field-container">
|
||||
<div class="text-container">
|
||||
<input :value="annualAmount" @blur="validateAnnualAmount" class="input-field"
|
||||
autocomplete="off"/>
|
||||
</div>
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
Unable to route from supplier to {{ this.destination.destination_node.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else key="rate" class="input-field-container">
|
||||
<div v-else key="rate" class="text-container">
|
||||
<input :value="rateD2d" @blur="validateRateD2d" class="input-field"
|
||||
autocomplete="off"/>
|
||||
</div>
|
||||
|
|
@ -173,7 +173,6 @@ export default {
|
|||
border-radius: 0.8rem;
|
||||
padding: 1.6rem;
|
||||
margin-bottom: 1.6rem;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
.destination-edit-cell-routing {
|
||||
|
|
@ -235,7 +234,7 @@ export default {
|
|||
min-width: 5rem;
|
||||
}
|
||||
|
||||
.input-field-container {
|
||||
.text-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: white;
|
||||
|
|
@ -246,7 +245,7 @@ export default {
|
|||
flex: 1 1 fit-content(80rem);
|
||||
}
|
||||
|
||||
.input-field-container:hover {
|
||||
.text-container:hover {
|
||||
background: #EEF4FF;
|
||||
border: 0.2rem solid #8DB3FE;
|
||||
transform: scale(1.01);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="select-material-modal-container">
|
||||
<h3 class="sub-header">Select material</h3>
|
||||
<h3 class="sub-header">Select part number</h3>
|
||||
<div class="select-material-container">
|
||||
<div class="select-material-caption-column">Part number</div>
|
||||
<div class="select-material-input-column select-material-input-field-suppliername">
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@
|
|||
<form @submit.prevent="send">
|
||||
<div class="create-new-node-form-container">
|
||||
<div class="input-field-caption">Name:</div>
|
||||
<div><div class="input-field-container"><input class="input-field" v-model="nodeName"/></div></div>
|
||||
<div><div class="text-container"><input class="input-field" v-model="nodeName"/></div></div>
|
||||
<div></div>
|
||||
<div class="input-field-caption">Address:</div>
|
||||
<div><div class="input-field-container"><input class="input-field" v-model="nodeAddress"/></div></div>
|
||||
<div><div class="text-container"><input class="input-field" v-model="nodeAddress"/></div></div>
|
||||
<div>
|
||||
<basic-button icon="SealCheck">Verify address</basic-button>
|
||||
</div>
|
||||
|
|
@ -89,7 +89,7 @@ export default {
|
|||
color: #002F54;
|
||||
}
|
||||
|
||||
.input-field-container {
|
||||
.text-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: white;
|
||||
|
|
@ -101,7 +101,7 @@ export default {
|
|||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.input-field-container:hover {
|
||||
.text-container:hover {
|
||||
background: #EEF4FF;
|
||||
border: 0.2rem solid #8DB3FE;
|
||||
/*transform: translateY(2px);*/
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
variant="flags"
|
||||
@selected="selected"
|
||||
ref="searchbar"
|
||||
:initial-value="initialValue"
|
||||
:activate-watcher="true"
|
||||
>
|
||||
</autosuggest-searchbar>
|
||||
</div>
|
||||
|
|
@ -51,10 +53,27 @@ import Checkbox from "@/components/UI/Checkbox.vue";
|
|||
export default {
|
||||
name: "SelectNode",
|
||||
components: {Checkbox, AutosuggestSearchbar, Flag, InputField, BasicButton},
|
||||
emits: ['close'],
|
||||
emits: ['updateSupplier'],
|
||||
props: {
|
||||
openSelectDirect: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
preSelectedNode: {
|
||||
type: Object,
|
||||
required: false
|
||||
},
|
||||
},
|
||||
created() {
|
||||
console.log("SelectNode created with openSelectDirect: " + this.openSelectDirect, this.preSelectedNode);
|
||||
if(this.openSelectDirect) {
|
||||
this.node = this.preSelectedNode;
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
action(action) {
|
||||
this.$emit('close', {action: action, nodeId: this.node?.id, updateMasterData: this.updateMasterData});
|
||||
this.$emit('updateSupplier', {action: action, nodeId: this.node?.id, updateMasterData: this.updateMasterData});
|
||||
},
|
||||
checkboxChanged(value) {
|
||||
this.updateMasterData = value;
|
||||
|
|
@ -106,6 +125,13 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
...mapStores(useNodeStore),
|
||||
initialValue() {
|
||||
if(this.node) {
|
||||
return this.node.name;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
supplierAddress() {
|
||||
return this.node?.address ?? '';
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
<!--TODO: isMassEdit-->
|
||||
|
||||
<template>
|
||||
<div class="edit-calculation-container">
|
||||
<div class="header-container">
|
||||
<h2 class="page-header">Mass edit calculation</h2>
|
||||
<div class="header-controls">
|
||||
<basic-button :show-icon="false" :disabled="premiseEditStore.selectedLoading"
|
||||
variant="secondary">Close
|
||||
<basic-button :show-icon="false"
|
||||
:disabled="premiseEditStore.selectedLoading"
|
||||
variant="secondary"
|
||||
@click="closeMassEdit"
|
||||
>Close
|
||||
</basic-button>
|
||||
<basic-button :show-icon="true"
|
||||
:disabled="premiseEditStore.selectedLoading"
|
||||
icon="Calculator" variant="primary">Calculate & close
|
||||
icon="Calculator" variant="primary"
|
||||
@click="startCalculation"
|
||||
>Calculate & close
|
||||
</basic-button>
|
||||
|
||||
</div>
|
||||
|
|
@ -80,15 +83,15 @@
|
|||
v-model:unitCount="componentProps.unitCount"
|
||||
v-model:mixable="componentProps.mixable"
|
||||
v-model:stackable="componentProps.stackable"
|
||||
v-model:supplierName="componentProps.supplierName"
|
||||
v-model:supplierAddress="componentProps.supplierAddress"
|
||||
v-model:supplierCoordinates="componentProps.supplierCoordinates"
|
||||
v-model:isoCode="componentProps.isoCode"
|
||||
v-model:preSelectedNode="componentProps.preSelectedNode"
|
||||
v-model:openSelectDirect="componentProps.openSelectDirect"
|
||||
@update-material="updateMaterial"
|
||||
@update-supplier="updateSupplier"
|
||||
@close="closeEditModalAction('cancel')"
|
||||
|
||||
>
|
||||
</component>
|
||||
<div class="modal-content-footer">
|
||||
<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
|
||||
</basic-button>
|
||||
|
|
@ -117,13 +120,14 @@ import MaterialEdit from "@/components/layout/edit/MaterialEdit.vue";
|
|||
import PackagingEdit from "@/components/layout/edit/PackagingEdit.vue";
|
||||
import SupplierView from "@/components/layout/edit/SupplierView.vue";
|
||||
import DestinationListView from "@/components/layout/edit/DestinationListView.vue";
|
||||
import SelectNode from "@/components/layout/node/SelectNode.vue";
|
||||
|
||||
|
||||
const COMPONENT_TYPES = {
|
||||
price: PriceEdit,
|
||||
material: MaterialEdit,
|
||||
packaging: PackagingEdit,
|
||||
supplier: SupplierView,
|
||||
supplier: SelectNode,
|
||||
destinations: DestinationListView,
|
||||
}
|
||||
|
||||
|
|
@ -153,6 +157,9 @@ export default {
|
|||
showMultiselectAction() {
|
||||
return this.selectCount > 0;
|
||||
},
|
||||
showModalFooter() {
|
||||
return this.modalType !== 'supplier';
|
||||
},
|
||||
showEditModal() {
|
||||
return ((this.modalType ?? null) !== null);
|
||||
},
|
||||
|
|
@ -181,7 +188,7 @@ export default {
|
|||
modalType: null,
|
||||
componentsData: {
|
||||
price: {props: {price: 0, overSeaShare: 0, includeFcaFee: true}},
|
||||
material: {props: {partNumber: "", hsCode: "", tariffRate: 0.00, description: ""}},
|
||||
material: {props: {partNumber: "", hsCode: "", tariffRate: 0.00, description: "", openSelectDirect: true}},
|
||||
packaging: {
|
||||
props: {
|
||||
length: 0,
|
||||
|
|
@ -197,10 +204,8 @@ export default {
|
|||
},
|
||||
supplier: {
|
||||
props: {
|
||||
supplierName: "",
|
||||
supplierAddress: "",
|
||||
supplierCoordinates: {latitude: 1, longitude: 2},
|
||||
isoCode: "DE"
|
||||
preSelectedNode: null,
|
||||
openSelectDirect: false
|
||||
}
|
||||
},
|
||||
destinations: {props: {}},
|
||||
|
|
@ -211,15 +216,29 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
async updateMaterial(id, action) {
|
||||
console.log(id, action);
|
||||
await this.premiseEditStore.setMaterial(id, action === 'updateMasterData', this.editIds);
|
||||
startCalculation() {
|
||||
this.premiseEditStore.startCalculation();
|
||||
},
|
||||
closeMassEdit() {
|
||||
this.$router.push({name: "calculation-list"});
|
||||
},
|
||||
async updateSupplier(data) {
|
||||
console.log("update supplier", data.nodeId, data.action, data.updateMasterData, this.editIds);
|
||||
this.modalType = null;
|
||||
if (data.action === 'accept') {
|
||||
await this.premiseEditStore.setSupplier(data.nodeId, data.updateMasterData, this.editIds);
|
||||
|
||||
if (this.dataSourceId !== null) {
|
||||
this.fillData("material", this.dataSourceId);
|
||||
}
|
||||
|
||||
},
|
||||
async updateMaterial(id, action) {
|
||||
console.log(id, action, this.editIds);
|
||||
await this.premiseEditStore.setMaterial(id, action === 'updateMasterData', this.editIds);
|
||||
|
||||
if (this.editIds[0] !== null) {
|
||||
this.fillData("material", this.editIds[0]);
|
||||
}
|
||||
},
|
||||
updateCheckBoxes(value) {
|
||||
this.premiseEditStore.setSelectTo(this.ids, value);
|
||||
},
|
||||
|
|
@ -235,13 +254,15 @@ export default {
|
|||
if (type !== 'destinations')
|
||||
this.fillData(type, dataSource)
|
||||
else {
|
||||
console.log(ids, dataSource, massEdit)
|
||||
this.premiseEditStore.prepareDestinations(dataSource, ids, massEdit, true);
|
||||
}
|
||||
|
||||
this.dataSourceId = dataSource !== -1 ? dataSource : null;
|
||||
this.editIds = ids;
|
||||
this.modalType = type;
|
||||
|
||||
console.log("open modal", massEdit, this.modalType, this.editIds, this.dataSourceId)
|
||||
|
||||
},
|
||||
closeEditModalAction(action) {
|
||||
|
||||
|
|
@ -249,7 +270,6 @@ export default {
|
|||
if (action === "accept") {
|
||||
this.premiseEditStore.executeDestinationsMassEdit();
|
||||
} else {
|
||||
console.log("cancel mass edit")
|
||||
this.premiseEditStore.cancelMassEdit();
|
||||
}
|
||||
} else {
|
||||
|
|
@ -282,7 +302,7 @@ export default {
|
|||
this.editIds.forEach(id => {
|
||||
const p = this.premiseEditStore.getById(id);
|
||||
p.handling_unit.weight = this.componentsData[this.modalType].props.weight;
|
||||
p.handling_unit.height = this.componentsData[this.modalType].props.height;
|
||||
p.handling_unit.width = this.componentsData[this.modalType].props.width;
|
||||
p.handling_unit.length = this.componentsData[this.modalType].props.length;
|
||||
p.handling_unit.height = this.componentsData[this.modalType].props.height;
|
||||
|
||||
|
|
@ -296,11 +316,7 @@ export default {
|
|||
|
||||
this.premiseEditStore.savePackaging(this.editIds);
|
||||
|
||||
} else if (this.modalType === "supplier") {
|
||||
//set supplier.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -310,13 +326,11 @@ export default {
|
|||
},
|
||||
fillData(type, id = -1) {
|
||||
|
||||
console.log("fillData", type, id);
|
||||
|
||||
if (id === -1) {
|
||||
// clear
|
||||
this.componentsData = {
|
||||
price: {props: {price: 0, overSeaShare: 0.0, includeFcaFee: true}},
|
||||
material: {props: {partNumber: "", hsCode: "", tariffRate: 0.00, description: ""}},
|
||||
material: {props: {partNumber: "", hsCode: "", tariffRate: 0.00, description: "", openSelectDirect: true}},
|
||||
packaging: {
|
||||
props: {
|
||||
length: 0,
|
||||
|
|
@ -332,10 +346,8 @@ export default {
|
|||
},
|
||||
supplier: {
|
||||
props: {
|
||||
supplierName: "",
|
||||
supplierAddress: "",
|
||||
supplierCoordinates: {latitude: 1, longitude: 2},
|
||||
isoCode: "DE"
|
||||
preSelectedNode: null,
|
||||
openSelectDirect: false
|
||||
}
|
||||
},
|
||||
destinations: {props: {}},
|
||||
|
|
@ -350,12 +362,15 @@ export default {
|
|||
includeFcaFee: premise.is_fca_enabled
|
||||
}
|
||||
} else if (type === "material") {
|
||||
|
||||
this.componentsData.material.props = {
|
||||
partNumber: premise.material.part_number,
|
||||
hsCode: premise.material.hs_code ?? "",
|
||||
tariffRate: premise.tariff_rate ?? 0.00,
|
||||
description: premise.material.name ?? ""
|
||||
description: premise.material.name ?? "",
|
||||
openSelectDirect: false
|
||||
}
|
||||
|
||||
} else if (type === "packaging") {
|
||||
this.componentsData.packaging.props = {
|
||||
length: premise.handling_unit.length ?? 0,
|
||||
|
|
@ -370,13 +385,8 @@ export default {
|
|||
}
|
||||
} else if (type === "supplier") {
|
||||
this.componentsData.supplier.props = {
|
||||
supplierName: premise.supplier.name ?? "",
|
||||
supplierAddress: premise.supplier.address ?? "",
|
||||
supplierCoordinates: {
|
||||
latitude: premise.supplier.location.latitude,
|
||||
longitude: premise.supplier.location.longitude
|
||||
},
|
||||
isoCode: premise.supplier.country.iso_code
|
||||
preSelectedNode: premise.supplier,
|
||||
openSelectDirect: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,13 @@
|
|||
<div class="header-controls">
|
||||
<basic-button @click="close" :show-icon="false" :disabled="premiseEditStore.selectedLoading" variant="secondary"> {{ fromMassEdit ? 'Back' : 'Close' }}
|
||||
</basic-button>
|
||||
<basic-button v-if="!fromMassEdit" :show-icon="true" :disabled="premiseEditStore.selectedLoading || !premiseEditStore.isSingleSelect"
|
||||
icon="Calculator" variant="primary">Calculate & close
|
||||
<basic-button v-if="!fromMassEdit"
|
||||
:show-icon="true"
|
||||
:disabled="premiseEditStore.selectedLoading || !premiseEditStore.isSingleSelect"
|
||||
icon="Calculator"
|
||||
variant="primary"
|
||||
@click="startCalculation"
|
||||
>Calculate & close
|
||||
</basic-button>
|
||||
|
||||
</div>
|
||||
|
|
@ -123,6 +128,9 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
startCalculation() {
|
||||
this.premiseEditStore.startCalculation();
|
||||
},
|
||||
close() {
|
||||
if(this.bulkEditQuery) {
|
||||
//TODO: deselect and save
|
||||
|
|
|
|||
|
|
@ -1,14 +1,77 @@
|
|||
<template>
|
||||
<div class="edit-calculation-container">
|
||||
<div class="header-container">
|
||||
<h2 class="page-header">Configuration</h2>
|
||||
<div>
|
||||
<staged-changes></staged-changes>
|
||||
<box class="box-container">
|
||||
<tab-container :tabs="tabsConfig" class="tab-container">
|
||||
</tab-container>
|
||||
</box>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import BasicButton from "@/components/UI/BasicButton.vue";
|
||||
import TabContainer from "@/components/UI/TabContainer.vue";
|
||||
import {markRaw} from "vue";
|
||||
import Properties from "@/components/layout/config/Properties.vue";
|
||||
import Box from "@/components/UI/Box.vue";
|
||||
import CountryProperties from "@/components/layout/config/CountryProperties.vue";
|
||||
import StagedChanges from "@/components/layout/config/StagedChanges.vue";
|
||||
|
||||
export default {
|
||||
name: "Config"
|
||||
name: "Config",
|
||||
components: {StagedChanges, Box, TabContainer, BasicButton},
|
||||
data() {
|
||||
return {
|
||||
currentTab: null,
|
||||
tabsConfig: [
|
||||
{
|
||||
title: 'System properties',
|
||||
component: markRaw(Properties),
|
||||
},
|
||||
{
|
||||
title: 'Countries',
|
||||
component: markRaw(CountryProperties),
|
||||
},
|
||||
{
|
||||
title: 'Nodes',
|
||||
component: (null),
|
||||
},
|
||||
{
|
||||
title: 'Kilometer rates',
|
||||
component: (null),
|
||||
},
|
||||
{
|
||||
title: 'Container rates',
|
||||
component: (null),
|
||||
},
|
||||
{
|
||||
title: 'Materials & packaging',
|
||||
component: (null),
|
||||
},
|
||||
{
|
||||
title: 'Bulk operations',
|
||||
component: (null),
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2 class="page-header">Configuration</h2>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.box-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.6rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -12,6 +12,7 @@ const router = createRouter({
|
|||
{
|
||||
path: '/',
|
||||
redirect: '/calculations',
|
||||
name: 'calculation-list',
|
||||
|
||||
},
|
||||
{
|
||||
|
|
|
|||
157
src/frontend/src/store/country.js
Normal file
157
src/frontend/src/store/country.js
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
import {defineStore} from 'pinia'
|
||||
import {config} from '@/config'
|
||||
import {useErrorStore} from "@/store/error.js";
|
||||
import {useStageStore} from "@/store/stage.js";
|
||||
|
||||
export const useCountryStore = defineStore('countryStore', {
|
||||
state() {
|
||||
return {
|
||||
properties: null,
|
||||
periods: null,
|
||||
loadedPeriod: null,
|
||||
loading: false,
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
|
||||
},
|
||||
actions: {
|
||||
async setProperty(property) {
|
||||
if(this.properties === null) return;
|
||||
|
||||
console.log(property)
|
||||
|
||||
const prop = this.properties.find(p => p.external_mapping_id === property.id);
|
||||
|
||||
if((prop ?? null) === null) return;
|
||||
|
||||
const url = `${config.backendUrl}/properties/country/${property.country.iso_code}/${property.id}`;
|
||||
const body = { value: String(property.value)};
|
||||
|
||||
await this.performRequest('PUT', url, body, false);
|
||||
|
||||
prop.draft_value = property.reset ? null : property.value;
|
||||
|
||||
},
|
||||
async reload() {
|
||||
await this.loadPeriods();
|
||||
await this.loadCountries();
|
||||
|
||||
const stage = useStageStore();
|
||||
await stage.checkStagedChanges();
|
||||
},
|
||||
async loadPeriods() {
|
||||
this.loading = true;
|
||||
const url = `${config.backendUrl}/properties/periods`;
|
||||
this.periods = await this.performRequest('GET', url, null);
|
||||
this.loading = false;
|
||||
},
|
||||
async loadCountries(period = null) {
|
||||
this.loading = true;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (period !== null)
|
||||
params.append('property_set', period);
|
||||
const url = `${config.backendUrl}/countries/all/${params.size === 0 ? '' : '?'}${params.toString()}`;
|
||||
this.properties = await this.performRequest('GET', url, null);
|
||||
this.loadedPeriod = period;
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
async performRequest(method, url, body, expectResponse = true) {
|
||||
|
||||
const params = {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
if ((body ?? null) !== null) {
|
||||
params.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
const request = {url: url, params: params};
|
||||
console.log("Request:", request);
|
||||
|
||||
const response = await fetch(url, params
|
||||
).catch(e => {
|
||||
const error = {
|
||||
code: 'Network error.',
|
||||
message: "Please check your internet connection.",
|
||||
trace: null
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
|
||||
throw e;
|
||||
});
|
||||
|
||||
let data = null;
|
||||
if (expectResponse) {
|
||||
data = await response.json().catch(e => {
|
||||
const error = {
|
||||
code: 'Malformed response',
|
||||
message: "Malformed server response. Please contact support.",
|
||||
trace: null
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw e;
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = {
|
||||
code: data.error.code,
|
||||
title: data.error.title,
|
||||
message: data.error.message,
|
||||
trace: data.error.trace
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
}
|
||||
} else {
|
||||
if (!response.ok) {
|
||||
|
||||
const data = await response.json().catch(e => {
|
||||
const error = {
|
||||
code: "Return code error " + response.status,
|
||||
message: "Server returned wrong response code",
|
||||
trace: null
|
||||
}
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
|
||||
});
|
||||
|
||||
const error = {
|
||||
code: data.error.code,
|
||||
title: data.error.title,
|
||||
message: data.error.message,
|
||||
trace: data.error.trace
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Response:", data);
|
||||
return data;
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
|
@ -248,6 +248,23 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
|
|||
},
|
||||
actions: {
|
||||
|
||||
async startCalculation() {
|
||||
|
||||
const body = this.premisses.map(p => p.id);
|
||||
const url = `${config.backendUrl}/calculation/start/`;
|
||||
|
||||
const data = await this.performRequest('PUT', url, body).catch(e => {
|
||||
// do something
|
||||
})
|
||||
|
||||
if (data) {
|
||||
// do something
|
||||
}
|
||||
|
||||
|
||||
alert("Finished.");
|
||||
},
|
||||
|
||||
/**
|
||||
* DESTINATION stuff
|
||||
* =================
|
||||
|
|
@ -555,14 +572,18 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
|
|||
|
||||
const selectedId = this.singleSelectId;
|
||||
|
||||
this.processDestinationMassEdit = true;
|
||||
|
||||
const body = {supplier_node_id: id, update_master_data: updateMasterData};
|
||||
const url = `${config.backendUrl}/calculation/supplier/`;
|
||||
await this.setData(url, body);
|
||||
await this.setData(url, body, ids);
|
||||
|
||||
if (selectedId != null && this.destinations && !this.destinations.fromMassEditView) {
|
||||
this.prepareDestinations(selectedId, [selectedId]);
|
||||
}
|
||||
|
||||
this.processDestinationMassEdit = false;
|
||||
|
||||
},
|
||||
async setMaterial(id, updateMasterData, ids = null) {
|
||||
console.log("setMaterial");
|
||||
|
|
|
|||
162
src/frontend/src/store/properties.js
Normal file
162
src/frontend/src/store/properties.js
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
import {defineStore} from 'pinia'
|
||||
import {config} from '@/config'
|
||||
import {useErrorStore} from "@/store/error.js";
|
||||
import { useStageStore } from './stage.js'
|
||||
import {usePropertySetsStore} from "@/store/propertySets.js";
|
||||
|
||||
export const usePropertiesStore = defineStore('properties', {
|
||||
state() {
|
||||
return {
|
||||
periods: null,
|
||||
properties: null,
|
||||
loadedPeriod: null,
|
||||
loading: false,
|
||||
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
isLoading(state) {
|
||||
return state.loading;
|
||||
},
|
||||
getProperties(state) {
|
||||
return state.properties;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async reload() {
|
||||
await this.loadProperties();
|
||||
|
||||
const periods = usePropertySetsStore();
|
||||
await periods.loadPeriods();
|
||||
|
||||
const stage = useStageStore();
|
||||
await stage.checkStagedChanges();
|
||||
},
|
||||
async setProperty(property) {
|
||||
if(this.properties === null) return;
|
||||
|
||||
console.log(property)
|
||||
|
||||
const prop = this.properties.find(p => p.external_mapping_id === property.id);
|
||||
|
||||
if((prop ?? null) === null) return;
|
||||
|
||||
const url = `${config.backendUrl}/properties/system/${property.id}`;
|
||||
const body = { value: String(property.value)};
|
||||
|
||||
await this.performRequest('PUT', url, body, false);
|
||||
|
||||
prop.draft_value = property.reset ? null : property.value;
|
||||
|
||||
const stage = useStageStore();
|
||||
await stage.checkStagedChanges();
|
||||
|
||||
},
|
||||
async loadProperties(period = null) {
|
||||
this.loading = true;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (period !== null)
|
||||
params.append('property_set', period);
|
||||
const url = `${config.backendUrl}/properties/${params.size === 0 ? '' : '?'}${params.toString()}`;
|
||||
this.properties = await this.performRequest('GET', url, null);
|
||||
this.loadedPeriod = period;
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
async performRequest(method, url, body, expectResponse = true) {
|
||||
|
||||
const params = {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
if ((body ?? null) !== null) {
|
||||
params.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
const request = {url: url, params: params};
|
||||
console.log("Request:", request);
|
||||
|
||||
const response = await fetch(url, params
|
||||
).catch(e => {
|
||||
const error = {
|
||||
code: 'Network error.',
|
||||
message: "Please check your internet connection.",
|
||||
trace: null
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
|
||||
throw e;
|
||||
});
|
||||
|
||||
let data = null;
|
||||
if (expectResponse) {
|
||||
data = await response.json().catch(e => {
|
||||
const error = {
|
||||
code: 'Malformed response',
|
||||
message: "Malformed server response. Please contact support.",
|
||||
trace: null
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw e;
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = {
|
||||
code: data.error.code,
|
||||
title: data.error.title,
|
||||
message: data.error.message,
|
||||
trace: data.error.trace
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
}
|
||||
} else {
|
||||
if (!response.ok) {
|
||||
|
||||
const data = await response.json().catch(e => {
|
||||
const error = {
|
||||
code: "Return code error " + response.status,
|
||||
message: "Server returned wrong response code",
|
||||
trace: null
|
||||
}
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
|
||||
});
|
||||
|
||||
const error = {
|
||||
code: data.error.code,
|
||||
title: data.error.title,
|
||||
message: data.error.message,
|
||||
trace: data.error.trace
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Response:", data);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
});
|
||||
150
src/frontend/src/store/propertySets.js
Normal file
150
src/frontend/src/store/propertySets.js
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
import {defineStore} from 'pinia'
|
||||
import {config} from '@/config'
|
||||
import {useErrorStore} from "@/store/error.js";
|
||||
import { useStageStore } from './stage.js'
|
||||
|
||||
export const usePropertySetsStore = defineStore('propertySets', {
|
||||
state() {
|
||||
return {
|
||||
periods: null,
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
getPeriods(state) {
|
||||
return state.periods;
|
||||
},
|
||||
getCurrentPeriodId(state) {
|
||||
if (state.periods === null)
|
||||
return null;
|
||||
|
||||
for (const period of state.periods) {
|
||||
if (period.state === "DRAFT")
|
||||
return period.id;
|
||||
}
|
||||
|
||||
for (const period of state.periods) {
|
||||
if (period.state === "VALID")
|
||||
return period.id;
|
||||
}
|
||||
},
|
||||
getPeriodState(state) {
|
||||
return function(periodId) {
|
||||
if (state.periods === null)
|
||||
return null;
|
||||
|
||||
return state.periods.find(p => p.id === periodId)?.state;
|
||||
}
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
async invalidate(periodId) {
|
||||
|
||||
|
||||
const url = `${config.backendUrl}/properties/periods/${periodId}/`;
|
||||
this.periods = await this.performRequest('DELETE', url, null, false);
|
||||
|
||||
await this.reload();
|
||||
|
||||
},
|
||||
async loadPeriods() {
|
||||
this.loading = true;
|
||||
const url = `${config.backendUrl}/properties/periods`;
|
||||
this.periods = await this.performRequest('GET', url, null, );
|
||||
this.loading = false;
|
||||
},
|
||||
async performRequest(method, url, body, expectResponse = true) {
|
||||
|
||||
const params = {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
if ((body ?? null) !== null) {
|
||||
params.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
const request = {url: url, params: params};
|
||||
console.log("Request:", request);
|
||||
|
||||
const response = await fetch(url, params
|
||||
).catch(e => {
|
||||
const error = {
|
||||
code: 'Network error.',
|
||||
message: "Please check your internet connection.",
|
||||
trace: null
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
|
||||
throw e;
|
||||
});
|
||||
|
||||
let data = null;
|
||||
if (expectResponse) {
|
||||
data = await response.json().catch(e => {
|
||||
const error = {
|
||||
code: 'Malformed response',
|
||||
message: "Malformed server response. Please contact support.",
|
||||
trace: null
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw e;
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = {
|
||||
code: data.error.code,
|
||||
title: data.error.title,
|
||||
message: data.error.message,
|
||||
trace: data.error.trace
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
}
|
||||
} else {
|
||||
if (!response.ok) {
|
||||
|
||||
const data = await response.json().catch(e => {
|
||||
const error = {
|
||||
code: "Return code error " + response.status,
|
||||
message: "Server returned wrong response code",
|
||||
trace: null
|
||||
}
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
|
||||
});
|
||||
|
||||
const error = {
|
||||
code: data.error.code,
|
||||
title: data.error.title,
|
||||
message: data.error.message,
|
||||
trace: data.error.trace
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Response:", data);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
});
|
||||
122
src/frontend/src/store/stage.js
Normal file
122
src/frontend/src/store/stage.js
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import {defineStore} from 'pinia'
|
||||
import {config} from '@/config'
|
||||
import {useErrorStore} from "@/store/error.js";
|
||||
|
||||
export const useStageStore = defineStore('stage', {
|
||||
state() {
|
||||
return {
|
||||
stagedChanges: false
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
hasStagedChanges(state) {
|
||||
return state.stagedChanges;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async checkStagedChanges() {
|
||||
const url = `${config.backendUrl}/properties/staged_changes`;
|
||||
this.stagedChanges = await this.performRequest('GET', url, null);
|
||||
},
|
||||
async applyChanges() {
|
||||
const url = `${config.backendUrl}/properties/staged_changes`;
|
||||
await this.performRequest('PUT', url, null, false);
|
||||
this.stagedChanges = false;
|
||||
},
|
||||
async performRequest(method, url, body, expectResponse = true) {
|
||||
|
||||
const params = {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
if ((body ?? null) !== null) {
|
||||
params.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
const request = {url: url, params: params};
|
||||
console.log("Request:", request);
|
||||
|
||||
const response = await fetch(url, params
|
||||
).catch(e => {
|
||||
const error = {
|
||||
code: 'Network error.',
|
||||
message: "Please check your internet connection.",
|
||||
trace: null
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
|
||||
throw e;
|
||||
});
|
||||
|
||||
let data = null;
|
||||
if (expectResponse) {
|
||||
data = await response.json().catch(e => {
|
||||
const error = {
|
||||
code: 'Malformed response',
|
||||
message: "Malformed server response. Please contact support.",
|
||||
trace: null
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw e;
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = {
|
||||
code: data.error.code,
|
||||
title: data.error.title,
|
||||
message: data.error.message,
|
||||
trace: data.error.trace
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
}
|
||||
} else {
|
||||
if (!response.ok) {
|
||||
|
||||
const data = await response.json().catch(e => {
|
||||
const error = {
|
||||
code: "Return code error " + response.status,
|
||||
message: "Server returned wrong response code",
|
||||
trace: null
|
||||
}
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
|
||||
});
|
||||
|
||||
const error = {
|
||||
code: data.error.code,
|
||||
title: data.error.title,
|
||||
message: data.error.message,
|
||||
trace: data.error.trace
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Response:", data);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -2,13 +2,13 @@ package de.avatic.lcc.controller.configuration;
|
|||
|
||||
import de.avatic.lcc.dto.generic.PropertyDTO;
|
||||
import de.avatic.lcc.dto.generic.ValidityPeriodDTO;
|
||||
import de.avatic.lcc.dto.configuration.properties.SetPropertyDTO;
|
||||
import de.avatic.lcc.model.country.IsoCode;
|
||||
import de.avatic.lcc.service.access.CountryService;
|
||||
import de.avatic.lcc.service.access.PropertyService;
|
||||
import de.avatic.lcc.service.configuration.PropertyApprovalService;
|
||||
import de.avatic.lcc.util.exception.badrequest.NotFoundException;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jdk.jfr.Unsigned;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
|
@ -99,12 +99,12 @@ public class PropertyController {
|
|||
* Sets a system-wide property by external mapping ID.
|
||||
*
|
||||
* @param externalMappingId The external mapping ID for the property.
|
||||
* @param value The value to set for the property.
|
||||
* @param dto The value to set for the property.
|
||||
* @return ResponseEntity indicating the operation status.
|
||||
*/
|
||||
@PutMapping({"/system/{external_mapping_id}", "/system/{external_mapping_id}/"})
|
||||
public ResponseEntity<Void> setProperties(@PathVariable(name = "external_mapping_id") String externalMappingId, @RequestBody String value) {
|
||||
propertyService.setProperties(externalMappingId, value);
|
||||
public ResponseEntity<Void> setProperties(@PathVariable(name = "external_mapping_id") String externalMappingId, @RequestBody SetPropertyDTO dto) {
|
||||
propertyService.setProperties(externalMappingId, dto.getValue());
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import jakarta.validation.constraints.Min;
|
|||
public class DestinationUpdateDTO {
|
||||
|
||||
@JsonProperty("annual_amount")
|
||||
@Min(1)
|
||||
@Min(value= 1, message = "Amount must be greater than or equal 1")
|
||||
private Integer annualAmount;
|
||||
|
||||
@JsonProperty("repackaging_costs")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
package de.avatic.lcc.dto.configuration.properties;
|
||||
|
||||
public class SetPropertyDTO {
|
||||
|
||||
String value;
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package de.avatic.lcc.model.utils;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import de.avatic.lcc.util.exception.badrequest.InvalidArgumentException;
|
||||
|
||||
|
|
@ -28,6 +29,26 @@ public enum DimensionUnit {
|
|||
this.baseFactor = baseFactor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom deserializer that handles case-insensitive matching
|
||||
*/
|
||||
@JsonCreator
|
||||
public static DimensionUnit fromString(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (DimensionUnit unit : DimensionUnit.values()) {
|
||||
if (unit.displayedName.equalsIgnoreCase(value)) {
|
||||
return unit;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unknown DimensionUnit: " + value +
|
||||
". Valid values are: t, kg, g (case insensitive)");
|
||||
}
|
||||
|
||||
|
||||
@JsonValue
|
||||
public String getDisplayedName() {
|
||||
return displayedName;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package de.avatic.lcc.model.utils;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
/**
|
||||
|
|
@ -25,6 +26,25 @@ public enum WeightUnit {
|
|||
this.baseFactor = baseFactor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom deserializer that handles case-insensitive matching
|
||||
*/
|
||||
@JsonCreator
|
||||
public static WeightUnit fromString(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (WeightUnit unit : WeightUnit.values()) {
|
||||
if (unit.displayedName.equalsIgnoreCase(value)) {
|
||||
return unit;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unknown WeightUnit: " + value +
|
||||
". Valid values are: t, kg, g (case insensitive)");
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public String getDisplayedName() {
|
||||
return displayedName;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import de.avatic.lcc.dto.generic.PropertyDTO;
|
|||
import de.avatic.lcc.model.properties.SystemPropertyMappingId;
|
||||
import de.avatic.lcc.model.rates.ValidityPeriodState;
|
||||
import de.avatic.lcc.util.exception.internalerror.DatabaseException;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
|
@ -12,7 +13,9 @@ import org.springframework.transaction.annotation.Transactional;
|
|||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -40,15 +43,31 @@ public class PropertyRepository {
|
|||
public void setProperty(Integer setId, String mappingId, String value) {
|
||||
var typeId = getTypeIdByMappingId(mappingId);
|
||||
|
||||
String validValueQuery = """
|
||||
SELECT sp.property_value
|
||||
FROM system_property sp
|
||||
JOIN property_set ps ON ps.id = sp.property_set_id
|
||||
WHERE ps.state = ? AND sp.system_property_type_id = ?""";
|
||||
|
||||
String validValue = jdbcTemplate.queryForObject(validValueQuery, String.class,
|
||||
ValidityPeriodState.VALID.name(), typeId);
|
||||
|
||||
if (value.equals(validValue)) {
|
||||
String deleteQuery = "DELETE FROM system_property WHERE property_set_id = ? AND system_property_type_id = ?";
|
||||
jdbcTemplate.update(deleteQuery, setId, typeId);
|
||||
return;
|
||||
}
|
||||
|
||||
String query = """
|
||||
INSERT INTO system_property (property_set_id, system_property_type_id, property_value) VALUES (?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE property_value = ?""";
|
||||
|
||||
var affectedRows = jdbcTemplate.update(query, setId, typeId, value, value);
|
||||
|
||||
if(!(affectedRows > 0)) {
|
||||
if (!(affectedRows > 0)) {
|
||||
throw new DatabaseException("Could not update property value for property set " + setId + " and property type " + mappingId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -79,7 +98,7 @@ public class PropertyRepository {
|
|||
LEFT JOIN system_property AS sp ON sp.system_property_type_id = type.id
|
||||
LEFT JOIN property_set AS ps ON ps.id = sp.property_set_id AND ps.state IN (?, ?)
|
||||
GROUP BY type.id, type.name, type.data_type, type.external_mapping_id, type.validation_rule
|
||||
HAVING draftValue IS NOT NULL OR validValue IS NOT NULL;
|
||||
HAVING draftValue IS NOT NULL OR validValue IS NOT NULL ORDER BY type.name;
|
||||
""";
|
||||
|
||||
return jdbcTemplate.query(query, new PropertyMapper(), ValidityPeriodState.DRAFT.name(), ValidityPeriodState.VALID.name(), ValidityPeriodState.DRAFT.name(), ValidityPeriodState.VALID.name());
|
||||
|
|
@ -100,10 +119,10 @@ public class PropertyRepository {
|
|||
FROM system_property_type AS type
|
||||
LEFT JOIN system_property AS property ON property.system_property_type_id = type.id
|
||||
LEFT JOIN property_set AS propertySet ON propertySet.id = property.property_set_id
|
||||
WHERE propertySet.id = ?
|
||||
WHERE propertySet.id = ? AND (propertySet.state = ? OR propertySet.state = ?) ORDER BY type.name;
|
||||
""";
|
||||
|
||||
return jdbcTemplate.query(query, new PropertyMapper(), propertySetId);
|
||||
return jdbcTemplate.query(query, new PropertyMapper(), propertySetId, ValidityPeriodState.EXPIRED.name(), ValidityPeriodState.INVALID.name());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
|
|
@ -130,19 +149,32 @@ public class PropertyRepository {
|
|||
*
|
||||
* @param setId the ID of the draft property set to fill
|
||||
*/
|
||||
@Transactional
|
||||
public void fillDraft(Integer setId) {
|
||||
String query = """
|
||||
SELECT type.id AS typeId, property.property_value as value FROM system_property_type AS type
|
||||
LEFT JOIN system_property AS property ON property.system_property_type_id = type.id
|
||||
LEFT JOIN property_set AS propertySet ON propertySet.id = property.property_set_id WHERE propertySet.state = ?""";
|
||||
SELECT type.id AS typeId, property.property_value as value FROM system_property_type AS type
|
||||
LEFT JOIN system_property AS property ON property.system_property_type_id = type.id
|
||||
LEFT JOIN property_set AS propertySet ON propertySet.id = property.property_set_id
|
||||
WHERE propertySet.state = ?""";
|
||||
|
||||
try {
|
||||
List<Map<String, Object>> results = jdbcTemplate.queryForList(query, ValidityPeriodState.VALID.name());
|
||||
|
||||
jdbcTemplate.query(query, (rs, rowNum) -> {
|
||||
String insertQuery = "INSERT IGNORE INTO system_property (property_value, system_property_type_id, property_set_id) VALUES (?, ?, ?)";
|
||||
jdbcTemplate.update(insertQuery, rs.getString("value"), rs.getInt("typeId"), setId, ValidityPeriodState.VALID.name());
|
||||
return null;
|
||||
String insertQuery = """
|
||||
INSERT IGNORE INTO system_property (property_value, system_property_type_id, property_set_id)
|
||||
VALUES (?, ?, ?)""";
|
||||
|
||||
}, setId);
|
||||
List<Object[]> batchArgs = results.stream()
|
||||
.map(row -> new Object[]{row.get("value"), row.get("typeId"), setId})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!batchArgs.isEmpty()) {
|
||||
jdbcTemplate.batchUpdate(insertQuery, batchArgs);
|
||||
}
|
||||
|
||||
} catch (DataAccessException e) {
|
||||
throw new DatabaseException("Failed to fill draft property set with ID: " + setId);
|
||||
}
|
||||
}
|
||||
|
||||
private static class PropertyMapper implements RowMapper<PropertyDTO> {
|
||||
|
|
|
|||
|
|
@ -132,6 +132,12 @@ public class PropertySetRepository {
|
|||
return totalCount != null && totalCount > 0;
|
||||
}
|
||||
|
||||
public ValidityPeriodState getState(Integer propertySetId) {
|
||||
String query = "SELECT state FROM property_set WHERE id = ?";
|
||||
String stateString = jdbcTemplate.queryForObject(query, String.class, propertySetId);
|
||||
return stateString != null ? ValidityPeriodState.valueOf(stateString) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapper class for converting SQL query results into {@link PropertySet} objects.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package de.avatic.lcc.service.access;
|
|||
import de.avatic.lcc.dto.generic.PropertyDTO;
|
||||
import de.avatic.lcc.dto.generic.ValidityPeriodDTO;
|
||||
import de.avatic.lcc.model.properties.SystemPropertyMappingId;
|
||||
import de.avatic.lcc.model.rates.ValidityPeriodState;
|
||||
import de.avatic.lcc.repositories.properties.PropertyRepository;
|
||||
import de.avatic.lcc.repositories.properties.PropertySetRepository;
|
||||
import de.avatic.lcc.service.transformer.rates.ValidityPeriodTransformer;
|
||||
|
|
@ -82,7 +83,11 @@ public class PropertyService {
|
|||
* @return a list of properties as {@link PropertyDTO} objects
|
||||
*/
|
||||
public List<PropertyDTO> listProperties(Integer propertySetId) {
|
||||
if (propertySetId == 0) {
|
||||
|
||||
|
||||
var state = propertySetId != 0 ? propertySetRepository.getState(propertySetId) : null;
|
||||
|
||||
if (propertySetId == 0 || (state != ValidityPeriodState.EXPIRED && state != ValidityPeriodState.INVALID)) {
|
||||
return propertyRepository.listProperties();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -163,17 +163,19 @@ public class PropertyValidationService {
|
|||
if (!((List<String>) this.value).contains(value)) {
|
||||
throw new PropertyValidationException(propertyId, operator.getIdentifier(), ((List<String>) this.value).toString(), value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
if (!operator.evaluate(Double.parseDouble(value), (Double) this.value)) {
|
||||
throw new PropertyValidationException(propertyId, operator.getIdentifier(), ((Double) this.value).toString(), value);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!operator.evaluate(Double.parseDouble(value), (Double) this.value)) {
|
||||
throw new PropertyValidationException(propertyId, operator.getIdentifier(), ((Double) this.value).toString(), value);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new PropertyValidationException(propertyId, value, e);
|
||||
}
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
throw new PropertyValidationException(propertyId, value, e);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ public class CalculationExecutionService {
|
|||
CalculationJob calculation = calculationJobRepository.getCalculationJob(calculationId).orElseThrow();
|
||||
|
||||
if (CalculationJobState.SCHEDULED.equals(calculation.getJobState())) {
|
||||
Premise premise = premiseRepository.getPremiseById(calculation.getId()).orElseThrow();
|
||||
Premise premise = premiseRepository.getPremiseById(calculation.getPremiseId()).orElseThrow();
|
||||
|
||||
// material cost + fca cost
|
||||
var materialCost = premise.getMaterialCost();
|
||||
|
|
|
|||
|
|
@ -34,6 +34,6 @@ public class CalculationStatusService {
|
|||
|
||||
public Integer schedule(ArrayList<Integer> calculationIds) {
|
||||
//TODO
|
||||
return null;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,68 +12,68 @@ import org.springframework.stereotype.Service;
|
|||
@Service
|
||||
public class DimensionTransformer {
|
||||
|
||||
public DimensionDTO toDimensionDTO(PackagingDimension entity) {
|
||||
DimensionDTO dto = new DimensionDTO();
|
||||
public DimensionDTO toDimensionDTO(PackagingDimension entity) {
|
||||
DimensionDTO dto = new DimensionDTO();
|
||||
|
||||
dto.setId(entity.getId());
|
||||
dto.setType(entity.getType());
|
||||
dto.setLength(entity.getDimensionUnit().convertFromMM(entity.getLength()).doubleValue());
|
||||
dto.setWidth(entity.getDimensionUnit().convertFromMM(entity.getWidth()).doubleValue());
|
||||
dto.setHeight(entity.getDimensionUnit().convertFromMM(entity.getHeight()).doubleValue());
|
||||
dto.setDimensionUnit(entity.getDimensionUnit());
|
||||
dto.setWeight(entity.getWeightUnit().convertFromG(entity.getWeight()).doubleValue());
|
||||
dto.setWeightUnit(entity.getWeightUnit());
|
||||
dto.setContentUnitCount(entity.getContentUnitCount());
|
||||
dto.setDeprecated(entity.getDeprecated());
|
||||
dto.setId(entity.getId());
|
||||
dto.setType(entity.getType());
|
||||
dto.setLength(entity.getDimensionUnit().convertFromMM(entity.getLength()).doubleValue());
|
||||
dto.setWidth(entity.getDimensionUnit().convertFromMM(entity.getWidth()).doubleValue());
|
||||
dto.setHeight(entity.getDimensionUnit().convertFromMM(entity.getHeight()).doubleValue());
|
||||
dto.setDimensionUnit(entity.getDimensionUnit());
|
||||
dto.setWeight(entity.getWeightUnit().convertFromG(entity.getWeight()).doubleValue());
|
||||
dto.setWeightUnit(entity.getWeightUnit());
|
||||
dto.setContentUnitCount(entity.getContentUnitCount());
|
||||
dto.setDeprecated(entity.getDeprecated());
|
||||
|
||||
return dto;
|
||||
}
|
||||
return dto;
|
||||
}
|
||||
|
||||
public PackagingDimension toDimensionEntity(DimensionDTO dto) {
|
||||
public PackagingDimension toDimensionEntity(DimensionDTO dto) {
|
||||
|
||||
if(dto.getDimensionUnit() == null) {
|
||||
throw new InvalidArgumentException("dimension_unit", "null");
|
||||
}
|
||||
if (dto.getDimensionUnit() == null) {
|
||||
throw new InvalidArgumentException("dimension_unit", "null");
|
||||
}
|
||||
|
||||
if(dto.getWeightUnit() == null) {
|
||||
throw new InvalidArgumentException("weight_unit", "null");
|
||||
}
|
||||
if (dto.getWeightUnit() == null) {
|
||||
throw new InvalidArgumentException("weight_unit", "null");
|
||||
}
|
||||
|
||||
var entity = new PackagingDimension();
|
||||
entity.setId(dto.getId());
|
||||
entity.setType(dto.getType());
|
||||
var entity = new PackagingDimension();
|
||||
entity.setId(dto.getId());
|
||||
entity.setType(dto.getType());
|
||||
|
||||
entity.setLength(doDimensionConversion(dto.getLength(), dto.getDimensionUnit()));
|
||||
entity.setWidth(doDimensionConversion(dto.getWidth(), dto.getDimensionUnit()));
|
||||
entity.setHeight( doDimensionConversion(dto.getHeight(), dto.getDimensionUnit()));
|
||||
entity.setDimensionUnit(dto.getDimensionUnit());
|
||||
entity.setWeight(doWeightConversion(dto.getWeight(), dto.getWeightUnit()));
|
||||
entity.setWeightUnit(dto.getWeightUnit());
|
||||
entity.setContentUnitCount(dto.getContentUnitCount());
|
||||
entity.setDeprecated(dto.getDeprecated());
|
||||
entity.setLength(doDimensionConversion(dto.getLength(), dto.getDimensionUnit()));
|
||||
entity.setWidth(doDimensionConversion(dto.getWidth(), dto.getDimensionUnit()));
|
||||
entity.setHeight(doDimensionConversion(dto.getHeight(), dto.getDimensionUnit()));
|
||||
entity.setDimensionUnit(dto.getDimensionUnit());
|
||||
entity.setWeight(doWeightConversion(dto.getWeight(), dto.getWeightUnit()));
|
||||
entity.setWeightUnit(dto.getWeightUnit());
|
||||
entity.setContentUnitCount(dto.getContentUnitCount());
|
||||
entity.setDeprecated(dto.getDeprecated());
|
||||
|
||||
return entity;
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
private Integer doWeightConversion(Double value, WeightUnit unit) {
|
||||
if(value == null) return null;
|
||||
if (value == null) return null;
|
||||
|
||||
if(value <= 0) {
|
||||
if (value <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return unit.convertToG(value);
|
||||
}
|
||||
|
||||
private Integer doDimensionConversion(Double value, DimensionUnit unit) {
|
||||
if(value == null) return null;
|
||||
private Integer doDimensionConversion(Double value, DimensionUnit unit) {
|
||||
if (value == null) return null;
|
||||
|
||||
if(value <= 0) {
|
||||
return null;
|
||||
}
|
||||
if (value <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return unit.convertToMM(value);
|
||||
}
|
||||
return unit.convertToMM(value);
|
||||
}
|
||||
|
||||
public PackagingDimension toDimensionEntity(Premise entity) {
|
||||
var packaging = new PackagingDimension();
|
||||
|
|
@ -97,11 +97,11 @@ public class DimensionTransformer {
|
|||
|
||||
dto.setId(null);
|
||||
dto.setType(PackagingType.HU);
|
||||
dto.setLength(entity.getHuDisplayedDimensionUnit().convertFromMM(entity.getIndividualHuLength()).doubleValue());
|
||||
dto.setWidth(entity.getHuDisplayedDimensionUnit().convertFromMM(entity.getIndividualHuWidth()).doubleValue());
|
||||
dto.setHeight(entity.getHuDisplayedDimensionUnit().convertFromMM(entity.getIndividualHuHeight()).doubleValue());
|
||||
dto.setLength(entity.getIndividualHuLength() == null ? null : entity.getHuDisplayedDimensionUnit().convertFromMM(entity.getIndividualHuLength()));
|
||||
dto.setWidth(entity.getIndividualHuWidth() == null ? null : entity.getHuDisplayedDimensionUnit().convertFromMM(entity.getIndividualHuWidth()));
|
||||
dto.setHeight(entity.getIndividualHuHeight() == null ? null : entity.getHuDisplayedDimensionUnit().convertFromMM(entity.getIndividualHuHeight()));
|
||||
dto.setDimensionUnit(entity.getHuDisplayedDimensionUnit());
|
||||
dto.setWeight(entity.getHuDisplayedWeightUnit().convertFromG(entity.getIndividualHuWeight()).doubleValue());
|
||||
dto.setWeight(entity.getIndividualHuWeight() == null ? null : entity.getHuDisplayedWeightUnit().convertFromG(entity.getIndividualHuWeight()));
|
||||
dto.setWeightUnit(entity.getHuDisplayedWeightUnit());
|
||||
dto.setContentUnitCount(entity.getHuUnitCount());
|
||||
dto.setDeprecated(null);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package de.avatic.lcc.service.transformer.premise;
|
|||
|
||||
import de.avatic.lcc.dto.calculation.PremiseDTO;
|
||||
import de.avatic.lcc.dto.calculation.edit.PremiseDetailDTO;
|
||||
import de.avatic.lcc.dto.generic.DimensionDTO;
|
||||
import de.avatic.lcc.dto.generic.LocationDTO;
|
||||
import de.avatic.lcc.dto.generic.NodeDTO;
|
||||
import de.avatic.lcc.dto.generic.NodeType;
|
||||
|
|
@ -96,10 +95,8 @@ public class PremiseTransformer {
|
|||
dto.setMixable(entity.getHuMixable());
|
||||
dto.setStackable(entity.getHuStackable());
|
||||
|
||||
if (entity.getIndividualHuHeight() == null || entity.getIndividualHuWidth() == null || entity.getIndividualHuLength() == null || entity.getIndividualHuWeight() == null)
|
||||
dto.setDimension(new DimensionDTO());
|
||||
else
|
||||
dto.setDimension(dimensionTransformer.toDimensionDTO(entity));
|
||||
|
||||
dto.setDimension(dimensionTransformer.toDimensionDTO(entity));
|
||||
|
||||
if (entity.getSupplierNodeId() != null)
|
||||
dto.setSupplier(nodeRepository.getById(entity.getSupplierNodeId()).map(nodeTransformer::toNodeDTO).orElseThrow());
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue