FRONTEND/BACKEND: fixing bugs in schema contstraints. Fixing performance issues in mass calculation (frontend)
This commit is contained in:
parent
32feeb06a0
commit
c47531a335
19 changed files with 758 additions and 574 deletions
911
src/frontend/package-lock.json
generated
911
src/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -14,6 +14,7 @@
|
|||
"dependencies": {
|
||||
"@phosphor-icons/vue": "^2.2.1",
|
||||
"@vueuse/core": "^13.6.0",
|
||||
"loglevel": "^1.9.2",
|
||||
"pinia": "^3.0.3",
|
||||
"vue": "^3.5.18",
|
||||
"vue-router": "^4.5.1"
|
||||
|
|
|
|||
|
|
@ -154,3 +154,5 @@ export class UrlSafeBase64 {
|
|||
return urlSafeBase64.replace(/-/g, '+').replace(/_/g, '/');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
<template>
|
||||
<div class="checkbox-container">
|
||||
<label class="checkbox-item" @change="setFilter">
|
||||
<input type="checkbox" checked v-model="isChecked" >
|
||||
<label class="checkbox-item" :class="{ disabled: disabled }" @change="setFilter">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="isChecked"
|
||||
:disabled="disabled"
|
||||
v-model="isChecked"
|
||||
>
|
||||
<span class="checkmark"></span>
|
||||
<span class="checkbox-label"><slot></slot></span>
|
||||
</label>
|
||||
|
|
@ -16,6 +21,11 @@ export default{
|
|||
type: Boolean,
|
||||
default: true,
|
||||
required: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
name: "Checkbox",
|
||||
|
|
@ -30,6 +40,7 @@ export default{
|
|||
return this.internalChecked;
|
||||
},
|
||||
set(value) {
|
||||
if (this.disabled) return; // Prevent changes when disabled
|
||||
this.internalChecked = value;
|
||||
this.$emit('checkbox-changed', value);
|
||||
}
|
||||
|
|
@ -42,6 +53,7 @@ export default{
|
|||
},
|
||||
methods: {
|
||||
setFilter(event) {
|
||||
if (this.disabled) return; // Prevent action when disabled
|
||||
// The computed setter will handle the emit
|
||||
this.isChecked = event.target.checked;
|
||||
}
|
||||
|
|
@ -66,6 +78,11 @@ export default{
|
|||
user-select: none;
|
||||
}
|
||||
|
||||
.checkbox-item.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.checkbox-item input[type="checkbox"] {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
|
|
@ -74,6 +91,10 @@ export default{
|
|||
width: 0;
|
||||
}
|
||||
|
||||
.checkbox-item.disabled input[type="checkbox"] {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.checkmark {
|
||||
position: relative;
|
||||
height: 2rem;
|
||||
|
|
@ -88,18 +109,28 @@ export default{
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
.checkbox-item:hover .checkmark {
|
||||
.checkbox-item:not(.disabled):hover .checkmark {
|
||||
background: #EEF4FF;
|
||||
border: 0.2rem solid #8DB3FE;
|
||||
transform: scale(1.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.checkbox-item.disabled .checkmark {
|
||||
background-color: #f5f5f5;
|
||||
border-color: #d0d0d0;
|
||||
}
|
||||
|
||||
.checkbox-item input:checked ~ .checkmark {
|
||||
background-color: #002F54;
|
||||
border-color: #002F54;
|
||||
}
|
||||
|
||||
.checkbox-item.disabled input:checked ~ .checkmark {
|
||||
background-color: #6b7280;
|
||||
border-color: #6b7280;
|
||||
}
|
||||
|
||||
.checkmark::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
|
|
@ -123,4 +154,8 @@ export default{
|
|||
font-weight: 400;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.checkbox-item.disabled .checkbox-label {
|
||||
color: #8a8a8a;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
<div class="edit-calculation-checkbox-cell">
|
||||
<checkbox :checked="isSelected" @checkbox-changed="updateSelected"></checkbox>
|
||||
</div>
|
||||
<div class="edit-calculation-cell--material" :class="copyModeClass"
|
||||
<div class="edit-calculation-cell--material copyable-cell"
|
||||
@click="action('material')">
|
||||
<div class="edit-calculation-cell-line">{{ premise.material.part_number }}</div>
|
||||
<div class="edit-calculation-cell-line edit-calculation-cell-subline" v-if="premise.material.name">
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
{{ toPercent(premise.tariff_rate) }} %
|
||||
</div>
|
||||
</div>
|
||||
<div class="edit-calculation-cell--price" :class="copyModeClass" v-if="showPrice"
|
||||
<div class="edit-calculation-cell--price copyable-cell" 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:
|
||||
|
|
@ -28,11 +28,14 @@
|
|||
<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>
|
||||
<div class="edit-calculation-cell-line edit-calculation-cell-subline" v-if="showPriceIncomplete">
|
||||
<basic-badge variant="exception" icon="warning">INCOMPLETE</basic-badge>
|
||||
</div>
|
||||
</div>
|
||||
<div class="edit-calculation-empty" :class="copyModeClass" v-else @click="action('price')">
|
||||
<div class="edit-calculation-empty copyable-cell" v-else @click="action('price')">
|
||||
<basic-badge variant="exception" icon="warning">INCOMPLETE</basic-badge>
|
||||
</div>
|
||||
<div v-if="showHu" class="edit-calculation-cell edit-calculation-cell--packaging" :class="copyModeClass"
|
||||
<div v-if="showHu" class="edit-calculation-cell edit-calculation-cell--packaging copyable-cell"
|
||||
@click="action('packaging')">
|
||||
<div class="edit-calculation-cell-line">
|
||||
<PhVectorThree/>
|
||||
|
|
@ -53,11 +56,11 @@
|
|||
<basic-badge v-if="premise.is_mixable" variant="skeleton" icon="shuffle">MIXABLE</basic-badge>
|
||||
</div>
|
||||
</div>
|
||||
<div class="edit-calculation-empty" :class="copyModeClass" v-else
|
||||
<div class="edit-calculation-empty copyable-cell" v-else
|
||||
@click="action('packaging')">
|
||||
<basic-badge variant="exception" icon="warning">INCOMPLETE</basic-badge>
|
||||
</div>
|
||||
<div class="edit-calculation-cell--supplier" :class="copyModeClass"
|
||||
<div class="edit-calculation-cell--supplier copyable-cell"
|
||||
@click="action('supplier')">
|
||||
<div class="edit-calculation-cell--supplier-container" v-if="premise.supplier">
|
||||
<!-- <div class="edit-calculation-cell--supplier-flag">-->
|
||||
|
|
@ -69,7 +72,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="edit-calculation-cell--destination" :class="copyModeClass" v-if="showDestinations"
|
||||
<div class="edit-calculation-cell--destination copyable-cell" v-if="showDestinations"
|
||||
@click="action('destinations')">
|
||||
<div class="edit-calculation-cell-line">
|
||||
<span class="number-circle"> {{ destinationsCount }} </span> Destinations
|
||||
|
|
@ -82,7 +85,7 @@
|
|||
<div class="edit-calculation-empty" v-else-if="showMassEdit">
|
||||
<spinner></spinner>
|
||||
</div>
|
||||
<div class="edit-calculation-empty" :class="copyModeClass" v-else
|
||||
<div class="edit-calculation-empty copyable-cell" v-else
|
||||
@click="action('destinations')">
|
||||
<basic-badge variant="exception" icon="warning">INCOMPLETE</basic-badge>
|
||||
</div>
|
||||
|
|
@ -109,7 +112,8 @@ import {
|
|||
PhBarcode,
|
||||
PhEmpty,
|
||||
PhFactory,
|
||||
PhHash, PhMapPin,
|
||||
PhHash,
|
||||
PhMapPin,
|
||||
PhPercent,
|
||||
PhVectorThree,
|
||||
PhVectorTwo
|
||||
|
|
@ -133,9 +137,9 @@ export default {
|
|||
type: Number,
|
||||
required: true
|
||||
},
|
||||
copyMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
premise: {
|
||||
type: Object,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -169,6 +173,9 @@ export default {
|
|||
showPrice() {
|
||||
return (this.premise.material_cost)
|
||||
},
|
||||
showPriceIncomplete() {
|
||||
return !(this.premise.oversea_share)
|
||||
},
|
||||
isSelected() {
|
||||
return this.premise.selected;
|
||||
},
|
||||
|
|
@ -176,16 +183,6 @@ export default {
|
|||
return this.premise.handling_unit;
|
||||
},
|
||||
...mapStores(usePremiseEditStore),
|
||||
premise() {
|
||||
const data = this.premiseEditStore.getById(this.id);
|
||||
return data;
|
||||
},
|
||||
copyModeClass() {
|
||||
if (this.copyMode) {
|
||||
return 'edit-calculation-cell--copy-mode';
|
||||
}
|
||||
return 'edit-calculation-cell';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toPercent(value) {
|
||||
|
|
@ -211,6 +208,19 @@ export default {
|
|||
|
||||
<style scoped>
|
||||
|
||||
.copyable-cell {
|
||||
padding: 0.8rem;
|
||||
border-radius: 0.8rem;
|
||||
height: 90%;
|
||||
}
|
||||
|
||||
/* Standard hover ohne copy mode */
|
||||
.copyable-cell:hover {
|
||||
cursor: pointer;
|
||||
background-color: rgba(107, 134, 156, 0.05);
|
||||
border-radius: 0.8rem;
|
||||
box-shadow: 0 0.4rem 0.6rem -0.1rem rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.bulk-edit-row {
|
||||
display: grid;
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
async saveProperty(property) {
|
||||
this.countryStore.setProperty(property);
|
||||
|
||||
},
|
||||
async query(query) {
|
||||
|
|
|
|||
|
|
@ -147,31 +147,12 @@ export default {
|
|||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* SOLUTION 1: Add relative positioning and min-height to prevent collapse */
|
||||
.properties-list {
|
||||
position: relative;
|
||||
min-height: 100px; /* Adjust based on your typical content height */
|
||||
min-height: 100px;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
/* SOLUTION 2: Keep elements in normal flow during transition
|
||||
.properties-fade-enter-active,
|
||||
.properties-fade-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.properties-fade-enter-from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.properties-fade-leave-to {
|
||||
opacity: 0;
|
||||
}*/
|
||||
|
||||
/* SOLUTION 3: For transition-group, ensure proper positioning
|
||||
.property-item-enter-active,
|
||||
.property-item-leave-active {
|
||||
transition: all 0.3s ease;
|
||||
}*/
|
||||
|
||||
.property-item-enter-from {
|
||||
opacity: 0;
|
||||
|
|
@ -193,16 +174,4 @@ export default {
|
|||
.property-item-move {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
/* ALTERNATIVE SOLUTION: If you still have issues, try this instead */
|
||||
/*
|
||||
.properties-container {
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
.properties-list {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
*/
|
||||
</style>
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
<div class="caption-column">
|
||||
<div class="caption-column-id">{{ property.name }}:</div>
|
||||
<div class="caption-column-name">Ext. Mapping: {{ property.external_mapping_id }}</div>
|
||||
</div>
|
||||
|
||||
<div class="input-column">
|
||||
|
|
@ -197,7 +198,6 @@ export default {
|
|||
grid-template-columns: 3fr 1fr 0.5fr;
|
||||
align-items: center;
|
||||
gap: 2.4rem;
|
||||
|
||||
height: 8rem;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -56,13 +56,13 @@
|
|||
</div>
|
||||
|
||||
<div class="input-column-chk">
|
||||
<tooltip position="left" text="Deselect if the handling unit cannot be stacked">
|
||||
<checkbox :checked="stackable" @checkbox-changed="updateStackable">stackable</checkbox>
|
||||
</tooltip>
|
||||
<tooltip position="left"
|
||||
text="Deselect if the handling unit cannot be transported together with other handling units">
|
||||
<checkbox :checked="mixable" @checkbox-changed="updateMixable">mixable</checkbox>
|
||||
</tooltip>
|
||||
<tooltip position="left" text="Deselect if the handling unit cannot be stacked">
|
||||
<checkbox :checked="stackable" @checkbox-changed="updateStackable" :disabled="mixable">stackable</checkbox>
|
||||
</tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -238,6 +238,10 @@ export default {
|
|||
this.$emit('update:stackable', value);
|
||||
},
|
||||
updateMixable(value) {
|
||||
|
||||
if(value)
|
||||
this.updateStackable(true);
|
||||
|
||||
this.$emit('update:mixable', value);
|
||||
},
|
||||
},
|
||||
|
|
|
|||
9
src/frontend/src/logger.js
Normal file
9
src/frontend/src/logger.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import log from 'loglevel'
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
log.setLevel('silent')
|
||||
} else {
|
||||
log.setLevel('debug')
|
||||
}
|
||||
|
||||
export default log
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="edit-calculation-container">
|
||||
<div class="edit-calculation-container" :class="{ 'has-selection': hasSelection }">
|
||||
<div class="header-container">
|
||||
<h2 class="page-header">Mass edit calculation</h2>
|
||||
<div class="header-controls">
|
||||
|
|
@ -43,8 +43,8 @@
|
|||
<span class="space-around">No Calculations found.</span>
|
||||
</div>
|
||||
|
||||
<bulk-edit-row v-else class="edit-calculation-list-item" v-for="id in this.premiseEditStore.getPremiseIds"
|
||||
:key="id" :id="id" @action="onClickAction" :copy-mode="selectCount !== 0">
|
||||
<bulk-edit-row v-else class="edit-calculation-list-item" v-for="premise of this.premiseEditStore.getPremisses"
|
||||
:key="premise.id" :id="premise.id" :premise="premise" @action="onClickAction">
|
||||
</bulk-edit-row>
|
||||
|
||||
|
||||
|
|
@ -118,7 +118,6 @@ import Modal from "@/components/UI/Modal.vue";
|
|||
import PriceEdit from "@/components/layout/edit/PriceEdit.vue";
|
||||
import MaterialEdit from "@/components/layout/edit/MaterialEdit.vue";
|
||||
import PackagingEdit from "@/components/layout/edit/PackagingEdit.vue";
|
||||
import SupplierView from "@/components/layout/edit/SupplierView.vue";
|
||||
import DestinationListView from "@/components/layout/edit/DestinationListView.vue";
|
||||
import SelectNode from "@/components/layout/node/SelectNode.vue";
|
||||
|
||||
|
|
@ -136,6 +135,12 @@ export default {
|
|||
components: {Modal, MassEditDialog, ListEdit, Spinner, CalculationListItem, Checkbox, BulkEditRow, BasicButton},
|
||||
computed: {
|
||||
...mapStores(usePremiseEditStore),
|
||||
hasSelection() {
|
||||
if (this.premiseEditStore.isLoading || this.premiseEditStore.selectedLoading) {
|
||||
return false;
|
||||
}
|
||||
return this.premiseEditStore.getSelectedPremissesIds?.length > 0;
|
||||
},
|
||||
selectCount() {
|
||||
return this.selectedPremisses?.length ?? 0;
|
||||
},
|
||||
|
|
@ -179,7 +184,7 @@ export default {
|
|||
created() {
|
||||
this.bulkQuery = this.$route.params.ids;
|
||||
this.ids = new UrlSafeBase64().decodeIds(this.$route.params.ids);
|
||||
this.premiseEditStore.loadPremissesForced(this.ids);
|
||||
this.premiseEditStore.loadPremissesIfNeeded(this.ids);
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -199,7 +204,7 @@ export default {
|
|||
dimensionUnit: "MM",
|
||||
unitCount: 1,
|
||||
mixable: true,
|
||||
stackable: false
|
||||
stackable: true
|
||||
}
|
||||
},
|
||||
supplier: {
|
||||
|
|
@ -223,6 +228,7 @@ export default {
|
|||
this.$router.push({name: "calculation-list"});
|
||||
},
|
||||
async updateSupplier(data) {
|
||||
console.log("update supplier", data.nodeId, data.action, data.updateMasterData, this.editIds);
|
||||
console.log("update supplier", data.nodeId, data.action, data.updateMasterData, this.editIds);
|
||||
this.modalType = null;
|
||||
if (data.action === 'accept') {
|
||||
|
|
@ -240,7 +246,7 @@ export default {
|
|||
}
|
||||
},
|
||||
updateCheckBoxes(value) {
|
||||
this.premiseEditStore.setSelectTo(this.ids, value);
|
||||
this.premiseEditStore.setAll(value);
|
||||
},
|
||||
multiselectAction(action) {
|
||||
this.openModal(action, this.selectedPremisses.map(p => p.id));
|
||||
|
|
@ -264,63 +270,30 @@ export default {
|
|||
console.log("open modal", massEdit, this.modalType, this.editIds, this.dataSourceId)
|
||||
|
||||
},
|
||||
closeEditModalAction(action) {
|
||||
|
||||
async closeEditModalAction(action) {
|
||||
if (this.modalType === "destinations") {
|
||||
if (action === "accept") {
|
||||
this.premiseEditStore.executeDestinationsMassEdit();
|
||||
await this.premiseEditStore.executeDestinationsMassEdit();
|
||||
} else {
|
||||
this.premiseEditStore.cancelMassEdit();
|
||||
}
|
||||
} else {
|
||||
if (action === "accept") {
|
||||
} else if (action === "accept") {
|
||||
const props = this.componentsData[this.modalType].props;
|
||||
|
||||
if (this.modalType === "price") {
|
||||
this.editIds.forEach(id => {
|
||||
const p = this.premiseEditStore.getById(id);
|
||||
p.material_cost = this.componentsData[this.modalType].props.price;
|
||||
p.oversea_share = this.componentsData[this.modalType].props.overSeaShare;
|
||||
p.is_fca_enabled = this.componentsData[this.modalType].props.includeFcaFee;
|
||||
});
|
||||
|
||||
this.premiseEditStore.savePrice(this.editIds);
|
||||
|
||||
} else if (this.modalType === "material") {
|
||||
|
||||
this.editIds.forEach(id => {
|
||||
const p = this.premiseEditStore.getById(id);
|
||||
p.material.part_number = this.componentsData[this.modalType].props.partNumber;
|
||||
p.material.hs_code = this.componentsData[this.modalType].props.hsCode;
|
||||
p.tariff_rate = this.componentsData[this.modalType].props.tariffRate;
|
||||
});
|
||||
|
||||
this.premiseEditStore.saveMaterial(this.editIds);
|
||||
|
||||
|
||||
} else if (this.modalType === "packaging") {
|
||||
|
||||
this.editIds.forEach(id => {
|
||||
const p = this.premiseEditStore.getById(id);
|
||||
p.handling_unit.weight = this.componentsData[this.modalType].props.weight;
|
||||
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;
|
||||
|
||||
p.handling_unit.weight_unit = this.componentsData[this.modalType].props.weightUnit;
|
||||
p.handling_unit.dimension_unit = this.componentsData[this.modalType].props.dimensionUnit;
|
||||
p.handling_unit.content_unit_count = this.componentsData[this.modalType].props.unitCount;
|
||||
|
||||
p.is_stackable = this.componentsData[this.modalType].props.stackable;
|
||||
p.is_mixable = this.componentsData[this.modalType].props.mixable;
|
||||
});
|
||||
|
||||
this.premiseEditStore.savePackaging(this.editIds);
|
||||
|
||||
}
|
||||
switch(this.modalType) {
|
||||
case "price":
|
||||
await this.premiseEditStore.batchUpdatePrice(this.editIds, props);
|
||||
break;
|
||||
case "material":
|
||||
await this.premiseEditStore.batchUpdateMaterial(this.editIds, props);
|
||||
break;
|
||||
case "packaging":
|
||||
await this.premiseEditStore.batchUpdatePackaging(this.editIds, props);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// clear data.
|
||||
// Clear data
|
||||
this.fillData(this.modalType);
|
||||
this.modalType = null;
|
||||
},
|
||||
|
|
@ -341,7 +314,7 @@ export default {
|
|||
dimensionUnit: "MM",
|
||||
unitCount: 1,
|
||||
mixable: true,
|
||||
stackable: false
|
||||
stackable: true
|
||||
}
|
||||
},
|
||||
supplier: {
|
||||
|
|
@ -394,9 +367,16 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
/* Global style für copy-mode cursor */
|
||||
.edit-calculation-container.has-selection :deep(.copyable-cell:hover) {
|
||||
cursor: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyOCIgaGVpZ2h0PSIyOCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPgogIDxyZWN0IHg9Ijg0IiB5PSIzMiIgd2lkdGg9IjEzNiIgaGVpZ2h0PSIxMzYiIGZpbGw9IndoaXRlIiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS13aWR0aD0iOCIgcng9IjQiLz4KICA8cmVjdCB4PSIzNiIgeT0iODQiIHdpZHRoPSIxMzYiIGhlaWdodD0iMTM2IiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjgiIHJ4PSI0Ii8+Cjwvc3ZnPg==") 12 12, pointer;
|
||||
background-color: rgba(107, 134, 156, 0.05);
|
||||
border-radius: 0.8rem;
|
||||
box-shadow: 0 0.4rem 0.6rem -0.1rem rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.space-around {
|
||||
margin: 3rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,6 +134,7 @@ export default {
|
|||
close() {
|
||||
if(this.bulkEditQuery) {
|
||||
//TODO: deselect and save
|
||||
this.premiseEditStore.deselectPremise();
|
||||
this.$router.push({name: 'bulk', params: {ids: this.bulkEditQuery}});
|
||||
}
|
||||
else {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ export const useCountryStore = defineStore('country', {
|
|||
state() {
|
||||
return {
|
||||
countries: null,
|
||||
properties: null,
|
||||
loading: false,
|
||||
query: null,
|
||||
selectedCountryId: null,
|
||||
|
|
@ -27,18 +26,21 @@ export const useCountryStore = defineStore('country', {
|
|||
},
|
||||
actions: {
|
||||
async setProperty(property) {
|
||||
if(this.properties === null) return;
|
||||
if(this.getSelectedCountry === null) return;
|
||||
|
||||
const prop = this.properties.find(p => p.external_mapping_id === property.id);
|
||||
const prop = this.getSelectedCountry.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 url = `${config.backendUrl}/properties/country/${this.getSelectedCountry.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;
|
||||
|
||||
const stage = useStageStore();
|
||||
await stage.checkStagedChanges();
|
||||
},
|
||||
async selectPeriod(periodId) {
|
||||
this.selectedPeriodId = periodId;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {defineStore} from 'pinia'
|
|||
import {config} from '@/config'
|
||||
import {toRaw} from "vue";
|
||||
import {useErrorStore} from "@/store/error.js";
|
||||
import logger from "@/logger.js"
|
||||
|
||||
export const usePremiseEditStore = defineStore('premiseEdit', {
|
||||
state() {
|
||||
|
|
@ -29,6 +30,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
|
|||
}
|
||||
},
|
||||
getters: {
|
||||
|
||||
/**
|
||||
* Returns the ids of all premises.
|
||||
* @param state
|
||||
|
|
@ -247,7 +249,66 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
|
|||
|
||||
},
|
||||
actions: {
|
||||
async batchUpdatePrice(ids, priceData) {
|
||||
const updatedPremises = this.premisses.map(p => {
|
||||
if (ids.includes(p.id)) {
|
||||
return {
|
||||
...p,
|
||||
material_cost: priceData.price,
|
||||
oversea_share: priceData.overSeaShare,
|
||||
is_fca_enabled: priceData.includeFcaFee
|
||||
};
|
||||
}
|
||||
return p;
|
||||
});
|
||||
this.premisses = updatedPremises;
|
||||
|
||||
return await this.savePrice(ids);
|
||||
},
|
||||
async batchUpdateMaterial(ids, materialData) {
|
||||
const updatedPremises = this.premisses.map(p => {
|
||||
if (ids.includes(p.id)) {
|
||||
return {
|
||||
...p,
|
||||
material: {
|
||||
...p.material,
|
||||
part_number: materialData.partNumber,
|
||||
hs_code: materialData.hsCode
|
||||
},
|
||||
tariff_rate: materialData.tariffRate
|
||||
};
|
||||
}
|
||||
return p;
|
||||
});
|
||||
this.premisses = updatedPremises;
|
||||
|
||||
return await this.saveMaterial(ids);
|
||||
},
|
||||
async batchUpdatePackaging(ids, packagingData) {
|
||||
const updatedPremises = this.premisses.map(p => {
|
||||
if (ids.includes(p.id)) {
|
||||
return {
|
||||
...p,
|
||||
handling_unit: {
|
||||
...p.handling_unit,
|
||||
weight: packagingData.weight,
|
||||
width: packagingData.width,
|
||||
length: packagingData.length,
|
||||
height: packagingData.height,
|
||||
weight_unit: packagingData.weightUnit,
|
||||
dimension_unit: packagingData.dimensionUnit,
|
||||
content_unit_count: packagingData.unitCount
|
||||
},
|
||||
is_stackable: packagingData.stackable,
|
||||
is_mixable: packagingData.mixable
|
||||
};
|
||||
}
|
||||
return p;
|
||||
});
|
||||
this.premisses = updatedPremises;
|
||||
|
||||
return await this.savePackaging(ids);
|
||||
},
|
||||
async startCalculation() {
|
||||
|
||||
const body = this.premisses.map(p => p.id);
|
||||
|
|
@ -406,7 +467,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
|
|||
selectDestination(id) {
|
||||
if (this.premisses === null) return;
|
||||
|
||||
console.log("selectDestination:", id)
|
||||
logger.info("selectDestination:", id)
|
||||
|
||||
const dest = this.destinations.destinations.find(d => d.id === id);
|
||||
|
||||
|
|
@ -446,7 +507,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
|
|||
route_selected_id: toDest.routes.find(r => r.is_selected)?.id ?? null,
|
||||
};
|
||||
|
||||
console.log(body)
|
||||
logger.info(body)
|
||||
|
||||
const url = `${config.backendUrl}/calculation/destination/${toDest.id}`;
|
||||
await this.performRequest('PUT', url, body, false);
|
||||
|
|
@ -483,15 +544,15 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
|
|||
|
||||
const url = `${config.backendUrl}/calculation/destination/${origId}`;
|
||||
await this.performRequest('DELETE', url, null, false).catch(async e => {
|
||||
console.error("Unable to delete destination: " + origId + "");
|
||||
console.error(e);
|
||||
logger.error("Unable to delete destination: " + origId + "");
|
||||
logger.error(e);
|
||||
await this.loadPremissesIfNeeded(this.premisses.map(p => p.id));
|
||||
});
|
||||
|
||||
for (const p of this.premisses) {
|
||||
const toBeDeleted = p.destinations.findIndex(d => String(d.id) === String(origId))
|
||||
|
||||
console.log(toBeDeleted)
|
||||
logger.info(toBeDeleted)
|
||||
|
||||
if (toBeDeleted !== -1) {
|
||||
p.destinations.splice(toBeDeleted, 1)
|
||||
|
|
@ -505,7 +566,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
|
|||
if (this.destinations.massEdit) {
|
||||
|
||||
const existing = this.destinations.destinations.find(d => d.destination_node.id === node.id);
|
||||
console.log(existing)
|
||||
logger.info(existing)
|
||||
|
||||
if ((existing ?? null) !== null) {
|
||||
console.info("Destination already exists", node.id);
|
||||
|
|
@ -568,7 +629,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
|
|||
*/
|
||||
|
||||
async setSupplier(id, updateMasterData, ids = null) {
|
||||
console.log("setSupplier");
|
||||
logger.info("setSupplier");
|
||||
|
||||
const selectedId = this.singleSelectId;
|
||||
|
||||
|
|
@ -586,7 +647,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
|
|||
|
||||
},
|
||||
async setMaterial(id, updateMasterData, ids = null) {
|
||||
console.log("setMaterial");
|
||||
logger.info("setMaterial");
|
||||
const body = {material_id: id, update_master_data: updateMasterData};
|
||||
const url = `${config.backendUrl}/calculation/material/`;
|
||||
await this.setData(url, body, ids);
|
||||
|
|
@ -600,7 +661,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
|
|||
this.loading = true;
|
||||
|
||||
body.premise_id = toBeUpdated;
|
||||
console.log(url, body)
|
||||
logger.info(url, body)
|
||||
|
||||
const data = await this.performRequest('PUT', url, body).catch(e => {
|
||||
this.loading = false;
|
||||
|
|
@ -626,7 +687,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
|
|||
replacePremissesById(premisses, loadedData) {
|
||||
const replacementMap = new Map(loadedData.map(obj => [obj.id, obj]));
|
||||
const replaced = premisses.map(obj => replacementMap.get(obj.id) || obj);
|
||||
console.log("Replaced", replaced);
|
||||
logger.info("Replaced", replaced);
|
||||
return replaced;
|
||||
},
|
||||
|
||||
|
|
@ -660,7 +721,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
|
|||
|
||||
if (!toBeUpdated?.length) return;
|
||||
|
||||
console.log(toBeUpdated[0]);
|
||||
logger.info(toBeUpdated[0]);
|
||||
|
||||
const body = {
|
||||
premise_ids: toBeUpdated.map(p => p.id),
|
||||
|
|
@ -726,10 +787,28 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
|
|||
this.selectedDestination = null;
|
||||
this.selectedLoading = false;
|
||||
},
|
||||
setAll(value) {
|
||||
|
||||
this.selectedLoading = true;
|
||||
|
||||
const updatedPremises = this.premisses.map(p => ({
|
||||
...p,
|
||||
selected: value
|
||||
}));
|
||||
this.premisses = updatedPremises;
|
||||
|
||||
this.selectedLoading = false;
|
||||
|
||||
},
|
||||
setSelectTo(ids, value) {
|
||||
this.selectedLoading = true;
|
||||
|
||||
this.premisses.forEach(p => p.selected = ids.includes(p.id) ? value : p.selected);
|
||||
const idsSet = new Set(ids);
|
||||
const updatedPremises = this.premisses.map(p => ({
|
||||
...p,
|
||||
selected: idsSet.has(p.id) ? value : p.selected
|
||||
}));
|
||||
this.premisses = updatedPremises;
|
||||
|
||||
this.selectedLoading = false;
|
||||
},
|
||||
|
|
@ -773,7 +852,6 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
|
|||
},
|
||||
async loadPremissesForced(ids) {
|
||||
|
||||
|
||||
this.loading = true;
|
||||
this.premises = [];
|
||||
|
||||
|
|
@ -808,7 +886,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
|
|||
}
|
||||
|
||||
const request = {url: url, params: params};
|
||||
console.log("Request:", request);
|
||||
logger.info("Request:", request);
|
||||
|
||||
const response = await fetch(url, params
|
||||
).catch(e => {
|
||||
|
|
@ -818,7 +896,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
|
|||
trace: null
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
logger.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
|
||||
|
|
@ -834,7 +912,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
|
|||
trace: null
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
logger.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw e;
|
||||
|
|
@ -848,7 +926,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
|
|||
trace: data.error.trace
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
logger.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
|
|
@ -862,7 +940,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
|
|||
message: "Server returned wrong response code",
|
||||
trace: null
|
||||
}
|
||||
console.error(error);
|
||||
logger.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
|
|
@ -876,7 +954,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
|
|||
trace: data.error.trace
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
logger.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
|
|
@ -885,7 +963,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
|
|||
}
|
||||
}
|
||||
|
||||
console.log("Response:", data);
|
||||
logger.info("Response:", data);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,12 +86,12 @@ public class PropertyController {
|
|||
*
|
||||
* @param isoCode The ISO code of the country.
|
||||
* @param mappingId 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({"/country/{iso}/{external_mapping_id}", "/country/{iso}/{external_mapping_id}/"})
|
||||
public ResponseEntity<Void> setCountryProperty(@PathVariable("iso") IsoCode isoCode, @PathVariable(name = "external_mapping_id") String mappingId, @RequestBody String value) {
|
||||
countryService.setProperties(isoCode, mappingId, value);
|
||||
public ResponseEntity<Void> setCountryProperty(@PathVariable("iso") IsoCode isoCode, @PathVariable(name = "external_mapping_id") String mappingId, @RequestBody SetPropertyDTO dto) {
|
||||
countryService.setProperties(isoCode, mappingId, dto.getValue());
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package de.avatic.lcc.repositories.country;
|
|||
|
||||
import de.avatic.lcc.dto.generic.PropertyDTO;
|
||||
import de.avatic.lcc.model.properties.CountryPropertyMappingId;
|
||||
import de.avatic.lcc.model.rates.ValidityPeriodState;
|
||||
import de.avatic.lcc.util.exception.internalerror.DatabaseException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
|
@ -28,6 +29,21 @@ public class CountryPropertyRepository {
|
|||
public void setProperty(Integer setId, Integer countryId, String mappingId, String value) {
|
||||
Integer typeId = getTypeIdByMappingId(mappingId);
|
||||
|
||||
String validValueQuery = """
|
||||
SELECT cp.property_value
|
||||
FROM country_property cp
|
||||
JOIN property_set ps ON ps.id = cp.property_set_id
|
||||
WHERE ps.state = ? AND cp.country_property_type_id = ? AND cp.country_id = ?""";
|
||||
|
||||
String validValue = jdbcTemplate.queryForObject(validValueQuery, String.class,
|
||||
ValidityPeriodState.VALID.name(), typeId, countryId);
|
||||
|
||||
if (value.equals(validValue)) {
|
||||
String deleteQuery = "DELETE FROM country_property WHERE property_set_id = ? AND country_property_type_id = ? AND country_id = ?";
|
||||
jdbcTemplate.update(deleteQuery, setId, typeId, countryId);
|
||||
return;
|
||||
}
|
||||
|
||||
String query = """
|
||||
INSERT INTO country_property (property_value, country_id, country_property_type_id, property_set_id) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE property_value = ?
|
||||
""";
|
||||
|
|
|
|||
|
|
@ -227,7 +227,9 @@ public class CalculationExecutionService {
|
|||
// Get container calculation
|
||||
for (var containerType : ContainerType.values()) {
|
||||
containerCalculation.put(containerType, containerCalculationService.doCalculation(hu, containerType));
|
||||
}
|
||||
|
||||
for (var containerType : ContainerType.values()) {
|
||||
if (!containerType.equals(ContainerType.TRUCK)) {
|
||||
|
||||
var sectionInfo = new ArrayList<SectionInfo>();
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@ INSERT INTO system_property_type ( name, external_mapping_id, data_type, validat
|
|||
|
||||
INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule) VALUES ( 'Payment terms [days]', 'PAYMENT_TERMS', 'INT', '{}');
|
||||
|
||||
|
||||
|
||||
INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule) VALUES ( 'Annual working days', 'WORKDAYS', 'INT', '{"GT": 0, "LT": 366}');
|
||||
INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule) VALUES ( 'Interest rate inventory [%]', 'INTEREST_RATE', 'PERCENTAGE', '{"GTE": 0}');
|
||||
INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule) VALUES ( 'FCA fee [%]', 'FCA_FEE', 'PERCENTAGE', '{"GTE": 0}');
|
||||
|
|
@ -325,6 +323,18 @@ VALUES (
|
|||
(SELECT spt.id FROM system_property_type spt WHERE spt.external_mapping_id = 'FEU_LOAD'),
|
||||
'21000'
|
||||
);
|
||||
|
||||
INSERT INTO system_property (property_set_id, system_property_type_id, property_value)
|
||||
VALUES (
|
||||
(SELECT ps.id FROM `property_set` ps
|
||||
WHERE ps.state = 'VALID'
|
||||
AND ps.start_date <= NOW()
|
||||
AND (ps.end_date IS NULL OR ps.end_date > NOW())
|
||||
ORDER BY ps.start_date DESC
|
||||
LIMIT 1),
|
||||
(SELECT spt.id FROM system_property_type spt WHERE spt.external_mapping_id = 'TRUCK_LOAD'),
|
||||
'25000'
|
||||
);
|
||||
|
||||
INSERT INTO system_property (property_set_id, system_property_type_id, property_value)
|
||||
VALUES (
|
||||
|
|
|
|||
|
|
@ -560,8 +560,7 @@ CREATE TABLE IF NOT EXISTS calculation_job_route_section
|
|||
FOREIGN KEY (calculation_job_destination_id) REFERENCES calculation_job_destination (id),
|
||||
INDEX idx_premise_route_section_id (premise_route_section_id),
|
||||
INDEX idx_calculation_job_destination_id (calculation_job_destination_id),
|
||||
CONSTRAINT chk_stacked CHECK (is_unmixed_price IS TRUE OR is_stacked IS TRUE), -- only unmixed transports can be unstacked
|
||||
CONSTRAINT chk_cbm_weight_price CHECK (is_unmixed_price IS FALSE OR
|
||||
(is_cbm_price IS FALSE AND is_weight_price IS FALSE))
|
||||
CONSTRAINT chk_stacked CHECK (is_unmixed_price IS FALSE OR is_stacked IS TRUE) -- only unmixed transports can be unstacked
|
||||
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue