BACKEND: several bugfixing in calculation and reporting
FRONTEND: Start implementing reporting
This commit is contained in:
parent
c47531a335
commit
ca3c15ecd2
28 changed files with 906 additions and 95 deletions
15
src/frontend/src/components/UI/ReportChart.vue
Normal file
15
src/frontend/src/components/UI/ReportChart.vue
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<script lang="ts">
|
||||
import {defineComponent} from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: "ReportChart"
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
15
src/frontend/src/components/UI/ReportRoute.vue
Normal file
15
src/frontend/src/components/UI/ReportRoute.vue
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<script lang="ts">
|
||||
import {defineComponent} from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: "ReportRoute"
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
|
||||
<template>
|
||||
<div class="item-container">
|
||||
<div class="item-container" :class="{'selected-item': selected}">
|
||||
<flag :iso="isoCode" size="l"></flag>
|
||||
<div class="supplier-item-text">
|
||||
<div class="supplier-item-name"> <span class="user-icon" v-if="isUserSupplier"><ph-user weight="fill" ></ph-user></span> {{name}}</div>
|
||||
<div class="supplier-item-address">{{ address }}</div>
|
||||
|
||||
</div>
|
||||
<icon-button icon="trash" @click="deleteClick"></icon-button>
|
||||
<icon-button v-if="showTrash" icon="trash" @click="deleteClick"></icon-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -41,6 +40,16 @@ export default {
|
|||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
selected: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
showTrash: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -53,6 +62,7 @@ export default {
|
|||
|
||||
<style scoped>
|
||||
|
||||
|
||||
.item-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
|
@ -63,13 +73,25 @@ export default {
|
|||
box-shadow: 0 0.4rem 0.6rem -0.1rem rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
gap: 2.4rem;
|
||||
flex: 0 0 50rem
|
||||
flex: 0 0 50rem;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.item-container:hover {
|
||||
background-color: rgba(107, 134, 156, 0.02);
|
||||
}
|
||||
|
||||
|
||||
.selected-item {
|
||||
background-color: #c3cfdf;
|
||||
}
|
||||
|
||||
.selected-item:hover {
|
||||
background-color: #a6b6ca;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.user-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
|
@ -79,7 +101,7 @@ export default {
|
|||
.supplier-item-name {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 600;
|
||||
color: #001D33;
|
||||
color: #002F54;
|
||||
display: flex;
|
||||
gap: 0.8rem;
|
||||
align-items: center;
|
||||
|
|
|
|||
|
|
@ -37,10 +37,10 @@
|
|||
></dropdown>
|
||||
</div>
|
||||
<transition name="properties-fade" mode="out-in">
|
||||
<div v-if="!loading" class="properties-list" :key="selectedPeriod">
|
||||
<div v-if="!loading" class="properties-list">
|
||||
<transition-group name="property-item" tag="div">
|
||||
<property v-for="property in properties"
|
||||
:key="`${selectedPeriod}-${selectedCountry.id}-${property.external_mapping_id}`"
|
||||
<property v-for="property of properties"
|
||||
:key="`${selectedPeriodId}-${selectedCountry.id}-${property.external_mapping_id}`"
|
||||
:property="property"
|
||||
:disabled="!isValidPeriodActive"
|
||||
@save="saveProperty"></property>
|
||||
|
|
@ -66,6 +66,7 @@ import IconButton from "@/components/UI/IconButton.vue";
|
|||
import Tooltip from "@/components/UI/Tooltip.vue";
|
||||
import ModalDialog from "@/components/UI/ModalDialog.vue";
|
||||
import Dropdown from "@/components/UI/Dropdown.vue";
|
||||
import {usePropertiesStore} from "@/store/properties.js";
|
||||
|
||||
export default {
|
||||
name: "CountryProperties",
|
||||
|
|
@ -94,12 +95,12 @@ export default {
|
|||
loading() {
|
||||
return this.countryStore.isLoading;
|
||||
},
|
||||
...mapStores(useCountryStore, usePropertySetsStore),
|
||||
...mapStores(useCountryStore, usePropertySetsStore, usePropertiesStore),
|
||||
countries() {
|
||||
return this.countryStore.getCountries;
|
||||
},
|
||||
properties() {
|
||||
return this.selectedCountry.properties;
|
||||
return this.countryStore.getSelectedCountry.properties;
|
||||
},
|
||||
isValidPeriodActive() {
|
||||
const state = this.propertySetsStore.getPeriodState(this.selectedPeriod);
|
||||
|
|
@ -108,18 +109,24 @@ export default {
|
|||
selectedCountry() {
|
||||
return this.countryStore.getSelectedCountry;
|
||||
},
|
||||
selectedPeriodId() {
|
||||
return this.propertySetsStore.getSelectedPeriod;
|
||||
},
|
||||
selectedPeriod: {
|
||||
get() {
|
||||
return this.propertySetsStore.getSelectedPeriod
|
||||
},
|
||||
set(value) {
|
||||
console.log(value)
|
||||
async set(value) {
|
||||
this.propertySetsStore.setSelectedPeriod(value);
|
||||
this.countryStore.selectPeriod(value);
|
||||
await this.countryStore.selectPeriod(value);
|
||||
await this.propertiesStore.loadProperties(value);
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
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')}`
|
||||
},
|
||||
async saveProperty(property) {
|
||||
this.countryStore.setProperty(property);
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import Tooltip from "@/components/UI/Tooltip.vue";
|
|||
import ModalDialog from "@/components/UI/ModalDialog.vue";
|
||||
import NotificationBar from "@/components/UI/NotificationBar.vue";
|
||||
import {usePropertySetsStore} from "@/store/propertySets.js";
|
||||
import {useCountryStore} from "@/store/country.js";
|
||||
|
||||
export default {
|
||||
name: "Properties",
|
||||
|
|
@ -58,7 +59,7 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(usePropertiesStore, usePropertySetsStore),
|
||||
...mapStores(usePropertiesStore, usePropertySetsStore, useCountryStore),
|
||||
loading() {
|
||||
return this.propertiesStore.isLoading;
|
||||
},
|
||||
|
|
@ -74,10 +75,10 @@ export default {
|
|||
get() {
|
||||
return this.propertySetsStore.getSelectedPeriod
|
||||
},
|
||||
set(value) {
|
||||
console.log(value)
|
||||
async set(value) {
|
||||
this.propertySetsStore.setSelectedPeriod(value);
|
||||
this.propertiesStore.loadProperties(value);
|
||||
await this.propertiesStore.loadProperties(value);
|
||||
await this.countryStore.selectPeriod(value);
|
||||
}
|
||||
},
|
||||
properties() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
<script>
|
||||
|
||||
export default {
|
||||
name: "DestinationCost"
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
14
src/frontend/src/components/layout/report/OverviewCost.vue
Normal file
14
src/frontend/src/components/layout/report/OverviewCost.vue
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<script>
|
||||
|
||||
export default {
|
||||
name: "OverviewCost"
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
14
src/frontend/src/components/layout/report/Report.vue
Normal file
14
src/frontend/src/components/layout/report/Report.vue
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<script>
|
||||
|
||||
export default {
|
||||
name: "SingleReport"
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
166
src/frontend/src/components/layout/report/SelectForReport.vue
Normal file
166
src/frontend/src/components/layout/report/SelectForReport.vue
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<div><h3 class="sub-header">Prepare report</h3></div>
|
||||
<div class="search-bar-container">
|
||||
<div class="caption">Find material:</div>
|
||||
<div class="search-bar">
|
||||
<autosuggest-searchbar @selected="searchReport"
|
||||
:fetch-suggestions="getMaterial"
|
||||
placeholder="Select material for reporting"
|
||||
title-resolver="part_number"
|
||||
:initial-value="partNumber"
|
||||
></autosuggest-searchbar>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-container" v-if="suppliers.length !== 0">
|
||||
<div class="caption">Select suppliers to compare:</div>
|
||||
<ul class="item-list">
|
||||
<li class="item-list-element" v-for="supplier in suppliers" :key="supplier.id"
|
||||
@click="selectSupplier(supplier.id)">
|
||||
<supplier-item :id="String(supplier.id)"
|
||||
:iso-code="supplier.country.iso_code"
|
||||
:address="supplier.address"
|
||||
:name="supplier.name"
|
||||
:show-trash="false"
|
||||
:is-user-supplier="supplier.isUserSupplier"
|
||||
:selected="isSelected(supplier.id)"
|
||||
></supplier-item>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="content-container-empty" v-else>no suppliers found.</div>
|
||||
<div class="footer">
|
||||
<basic-button :show-icon="false" @click="close('accept')" :disabled="!hasSelectedSuppliers">OK</basic-button>
|
||||
<basic-button :show-icon="false" variant="secondary" @click="close('discard')">Cancel</basic-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
import AutosuggestSearchbar from "@/components/UI/AutoSuggestSearchBar.vue";
|
||||
import BasicButton from "@/components/UI/BasicButton.vue";
|
||||
import {mapStores} from "pinia";
|
||||
import {useMaterialStore} from "@/store/material.js";
|
||||
import {useReportSearchStore} from "@/store/reportSearch.js";
|
||||
import SupplierItem from "@/components/layout/assistant/SupplierItem.vue";
|
||||
|
||||
export default {
|
||||
name: "SelectForReport",
|
||||
components: {SupplierItem, BasicButton, AutosuggestSearchbar},
|
||||
data() {
|
||||
return {
|
||||
selectedMaterialId: null,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.selectedMaterialId = this.reportSearchStore.getMaterial?.id;
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useMaterialStore, useReportSearchStore),
|
||||
suppliers() {
|
||||
return this.reportSearchStore.getSuppliers;
|
||||
},
|
||||
hasSelectedSuppliers() {
|
||||
return this.reportSearchStore.getSelectedIds.length > 0;
|
||||
},
|
||||
partNumber() {
|
||||
return this.reportSearchStore.getMaterial?.part_number;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
close(action) {
|
||||
const ids = this.reportSearchStore.getSelectedIds;
|
||||
this.$emit('close', {action: action, supplierIds: ids, materialId: this.selectedMaterialId});
|
||||
},
|
||||
isSelected(id) {
|
||||
return this.reportSearchStore.isSelected(id);
|
||||
},
|
||||
selectSupplier(id) {
|
||||
this.reportSearchStore.selectSupplier(id);
|
||||
},
|
||||
async getMaterial(query) {
|
||||
const materialQuery = {searchTerm: query};
|
||||
await this.materialStore.setQuery(materialQuery);
|
||||
return this.materialStore.materials;
|
||||
},
|
||||
async searchReport(material) {
|
||||
this.selectedMaterialId = material.id;
|
||||
this.reportSearchStore.setMaterial(material);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2.4rem;
|
||||
min-width: 90vw;
|
||||
min-height: min(80vh, 100rem);
|
||||
}
|
||||
|
||||
.search-bar-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.6rem;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.caption {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
flex: 1;
|
||||
overflow-x: auto; /* Enable horizontal scrolling */
|
||||
overflow-y: hidden; /* Prevent vertical overflow */
|
||||
min-height: 0; /* Allow flex item to shrink below content size */
|
||||
}
|
||||
|
||||
.content-container-empty {
|
||||
flex: 1;
|
||||
overflow-x: auto; /* Enable horizontal scrolling */
|
||||
overflow-y: hidden; /* Prevent vertical overflow */
|
||||
min-height: 0; /* Allow flex item to shrink below content size */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 400;
|
||||
color: #6B869C;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1.6rem;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.item-list {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
gap: 2.4rem 2.4rem;
|
||||
margin: 4.8rem;
|
||||
flex-wrap: nowrap;
|
||||
min-width: max-content;
|
||||
}
|
||||
|
||||
.item-list-element {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
14
src/frontend/src/components/layout/report/WeightedCost.vue
Normal file
14
src/frontend/src/components/layout/report/WeightedCost.vue
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<script>
|
||||
|
||||
export default {
|
||||
name: "ReportWeightedCost"
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
|
@ -26,7 +26,7 @@ import {
|
|||
PhArchive,
|
||||
PhFloppyDisk,
|
||||
PhArrowCounterClockwise,
|
||||
PhCheck, PhBug, PhShuffle, PhStack
|
||||
PhCheck, PhBug, PhShuffle, PhStack, PhFile
|
||||
} from "@phosphor-icons/vue";
|
||||
|
||||
const app = createApp(App);
|
||||
|
|
@ -59,6 +59,7 @@ app.component('PhStar', PhStar);
|
|||
app.component('PhBug', PhBug);
|
||||
app.component('PhShuffle', PhShuffle);
|
||||
app.component('PhStack', PhStack );
|
||||
app.component('PhFile', PhFile);
|
||||
|
||||
app.use(router);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,95 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="header-container">
|
||||
<h2 class="page-header">Reporting</h2>
|
||||
<div class="header-controls">
|
||||
<basic-button @click="showModal = true" icon="file">Create report</basic-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div v-if="hasReport">
|
||||
<box>
|
||||
</box>
|
||||
</div>
|
||||
<div v-else class="empty-container">
|
||||
<box><span class="space-around">No report selected</span></box>
|
||||
</div>
|
||||
|
||||
|
||||
<modal :state="showModal">
|
||||
<select-for-report @close="closeModal"></select-for-report>
|
||||
</modal>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import Modal from "@/components/UI/Modal.vue";
|
||||
import BasicButton from "@/components/UI/BasicButton.vue";
|
||||
import SelectForReport from "@/components/layout/report/SelectForReport.vue";
|
||||
import {mapStores} from "pinia";
|
||||
import {useReportsStore} from "@/store/reports.js";
|
||||
import Box from "@/components/UI/Box.vue";
|
||||
|
||||
export default {
|
||||
name: "Reporting"
|
||||
name: "Reporting",
|
||||
components: {Box, SelectForReport, BasicButton, Modal},
|
||||
data() {
|
||||
return {
|
||||
showModal: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useReportsStore),
|
||||
hasReport() {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
closeModal(data) {
|
||||
console.log("closeModal: ", data.action)
|
||||
if (data.action === 'accept') {
|
||||
console.log("create report")
|
||||
this.reportsStore.fetchReports(data.materialId, data.supplierIds);
|
||||
}
|
||||
this.showModal = false;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if(!this.hasReport)
|
||||
this.showModal = true;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2 class="page-header">Reporting</h2>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.space-around {
|
||||
margin: 3rem;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 1.6rem;
|
||||
}
|
||||
|
||||
.header-controls {
|
||||
display: flex;
|
||||
gap: 1.6rem;
|
||||
}
|
||||
|
||||
.empty-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 1.6rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -43,8 +43,11 @@ export const useCountryStore = defineStore('country', {
|
|||
await stage.checkStagedChanges();
|
||||
},
|
||||
async selectPeriod(periodId) {
|
||||
this.loading = true;
|
||||
this.countryDetail = null;
|
||||
this.selectedPeriodId = periodId;
|
||||
await this.loadCountryDetail();
|
||||
this.loading = false;
|
||||
},
|
||||
async selectCountry(countryId) {
|
||||
this.selectedCountryId = countryId;
|
||||
|
|
|
|||
185
src/frontend/src/store/reportSearch.js
Normal file
185
src/frontend/src/store/reportSearch.js
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
import {defineStore} from 'pinia'
|
||||
import {config} from '@/config'
|
||||
import {useErrorStore} from "@/store/error.js";
|
||||
import {useStageStore} from "@/store/stage.js";
|
||||
import {usePropertySetsStore} from "@/store/propertySets.js";
|
||||
import logger from "@/logger.js";
|
||||
|
||||
export const useReportSearchStore = defineStore('reportSearch', {
|
||||
state() {
|
||||
return {
|
||||
suppliers: [],
|
||||
selectedIds: [],
|
||||
remainingSuppliers: [],
|
||||
material: null,
|
||||
loading: false,
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
getSuppliers(state) {
|
||||
return state.remainingSuppliers;
|
||||
},
|
||||
isSelected(state) {
|
||||
return function (id) {
|
||||
return state.selectedIds.includes(id);
|
||||
}
|
||||
},
|
||||
getSelectedIds(state) {
|
||||
return state.selectedIds;
|
||||
},
|
||||
getMaterialId(state) {
|
||||
return state.material?.id;
|
||||
},
|
||||
getMaterial(state) {
|
||||
return state.material;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async selectSupplier(id) {
|
||||
|
||||
if (!this.selectedIds.includes(id))
|
||||
this.selectedIds.push(id)
|
||||
else {
|
||||
const index = this.selectedIds.findIndex(selectedId => selectedId === id);
|
||||
this.selectedIds.splice(index, 1);
|
||||
}
|
||||
|
||||
|
||||
this.updateShownSuppliers();
|
||||
},
|
||||
updateShownSuppliers() {
|
||||
const toBeShown = []
|
||||
|
||||
this.suppliers.forEach(supplierList => {
|
||||
const shouldInclude = this.selectedIds.every(id => supplierList.some(s => s.id === id))
|
||||
|
||||
if (shouldInclude)
|
||||
toBeShown.push(...supplierList);
|
||||
})
|
||||
|
||||
|
||||
this.remainingSuppliers = toBeShown.filter((item, index) =>
|
||||
toBeShown.findIndex(obj => obj.id === item.id) === index
|
||||
);
|
||||
},
|
||||
async setMaterial(material) {
|
||||
this.material = material;
|
||||
await this.updateSuppliers();
|
||||
},
|
||||
async updateSuppliers() {
|
||||
if (this.getMaterialId == null) return;
|
||||
|
||||
this.loading = true;
|
||||
this.suppliers = [];
|
||||
this.selectedIds = [];
|
||||
this.remainingSuppliers = [];
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.append('material', this.getMaterialId);
|
||||
const url = `${config.backendUrl}/reports/search/${params.size === 0 ? '' : '?'}${params.toString()}`;
|
||||
|
||||
this.suppliers = await this.performRequest('GET', url, null).catch(e => {
|
||||
this.loading = false;
|
||||
});
|
||||
|
||||
this.updateShownSuppliers();
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
async performRequest(method, url, body, expectResponse = true) {
|
||||
|
||||
const params = {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
if (body) {
|
||||
params.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
const request = {url: url, params: params};
|
||||
logger.info("Request:", request);
|
||||
|
||||
const response = await fetch(url, params
|
||||
).catch(e => {
|
||||
const error = {
|
||||
code: 'Network error.',
|
||||
message: "Please check your internet connection.",
|
||||
trace: null
|
||||
}
|
||||
|
||||
logger.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
|
||||
}
|
||||
|
||||
logger.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
|
||||
}
|
||||
|
||||
logger.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
|
||||
}
|
||||
logger.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
|
||||
}
|
||||
|
||||
logger.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Response:", data);
|
||||
return data;
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
135
src/frontend/src/store/reports.js
Normal file
135
src/frontend/src/store/reports.js
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
import {defineStore} from 'pinia'
|
||||
import {config} from '@/config'
|
||||
import {useErrorStore} from "@/store/error.js";
|
||||
import {useStageStore} from "@/store/stage.js";
|
||||
import {usePropertySetsStore} from "@/store/propertySets.js";
|
||||
import logger from "@/logger.js";
|
||||
|
||||
export const useReportsStore = defineStore('reports', {
|
||||
state() {
|
||||
return {
|
||||
reports: [],
|
||||
loading: false,
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
|
||||
},
|
||||
actions: {
|
||||
async fetchReports(materialId, supplierIds) {
|
||||
if (supplierIds == null || materialId == null) return;
|
||||
|
||||
this.loading = true;
|
||||
this.reports = [];
|
||||
|
||||
console.log("fetchreports")
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.append('material', materialId);
|
||||
params.append('sources', supplierIds);
|
||||
|
||||
const url = `${config.backendUrl}/reports/view/${params.size === 0 ? '' : '?'}${params.toString()}`;
|
||||
|
||||
this.reports = await this.performRequest('GET', url, null).catch(e => {
|
||||
this.loading = false;
|
||||
});
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
async performRequest(method, url, body, expectResponse = true) {
|
||||
|
||||
const params = {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
if (body) {
|
||||
params.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
const request = {url: url, params: params};
|
||||
logger.info("Request:", request);
|
||||
|
||||
const response = await fetch(url, params
|
||||
).catch(e => {
|
||||
const error = {
|
||||
code: 'Network error.',
|
||||
message: "Please check your internet connection.",
|
||||
trace: null
|
||||
}
|
||||
|
||||
logger.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
|
||||
}
|
||||
|
||||
logger.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
|
||||
}
|
||||
|
||||
logger.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
|
||||
}
|
||||
logger.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
|
||||
}
|
||||
|
||||
logger.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Response:", data);
|
||||
return data;
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
|
@ -8,7 +8,10 @@ import org.springframework.core.io.InputStreamResource;
|
|||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -40,7 +43,7 @@ public class ReportingController {
|
|||
* @param materialId The ID of the material for which suppliers need to be found.
|
||||
* @return A list of suppliers grouped by categories.
|
||||
*/
|
||||
@GetMapping("/search")
|
||||
@GetMapping({"/search", "/search/"})
|
||||
public ResponseEntity<List<List<NodeDTO>>> findSupplierForReporting(@RequestParam(value = "material") Integer materialId) {
|
||||
return ResponseEntity.ok(reportingService.findSupplierForReporting(materialId));
|
||||
}
|
||||
|
|
@ -52,7 +55,7 @@ public class ReportingController {
|
|||
* @param nodeIds A list of node IDs (sources) to include in the report.
|
||||
* @return The generated report details.
|
||||
*/
|
||||
@GetMapping("/view")
|
||||
@GetMapping({"/view","/view/"})
|
||||
public ResponseEntity<List<ReportDTO>> getReport(@RequestParam(value = "material") Integer materialId, @RequestParam(value = "sources") List<Integer> nodeIds) {
|
||||
return ResponseEntity.ok(reportingService.getReport(materialId, nodeIds));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ public class CalculationJobDestinationRepository {
|
|||
ps.setObject(paramIndex++, destination.getAnnualAirFreightCost());
|
||||
|
||||
// Transportation
|
||||
ps.setObject(paramIndex++, destination.getContainerType() != null ? destination.getContainerType().name() : null);
|
||||
ps.setObject(paramIndex++, destination.getContainerType().name());
|
||||
ps.setObject(paramIndex++, destination.getHuCount());
|
||||
ps.setObject(paramIndex++, destination.getLayerStructure());
|
||||
ps.setObject(paramIndex++, destination.getLayerCount());
|
||||
|
|
@ -179,6 +179,8 @@ public class CalculationJobDestinationRepository {
|
|||
entity.setTransportWeightExceeded(rs.getBoolean("transport_weight_exceeded"));
|
||||
entity.setShippingFrequency(rs.getInt("shipping_frequency"));
|
||||
entity.setAnnualTransportationCost(rs.getBigDecimal("annual_transportation_cost"));
|
||||
entity.setTotalTransitTime(rs.getInt("transit_time_in_days"));
|
||||
entity.setContainerUtilization(rs.getBigDecimal("container_utilization"));
|
||||
|
||||
// Material Cost fields
|
||||
entity.setMaterialCost(rs.getBigDecimal("material_cost"));
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public class RouteRepository {
|
|||
}
|
||||
|
||||
public Optional<Route> getSelectedByDestinationId(Integer id) {
|
||||
String query = "SELECT * FROM premise_route WHERE premise_destination_id = ?";
|
||||
String query = "SELECT * FROM premise_route WHERE premise_destination_id = ? AND is_selected = TRUE";
|
||||
var route = jdbcTemplate.query(query, new RouteMapper(), id);
|
||||
|
||||
if(route.isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -202,7 +202,14 @@ public class ValidityPeriodRepository {
|
|||
""";
|
||||
|
||||
|
||||
var periods = jdbcTemplate.query(validityPeriodSql, new ValidityPeriodMapper(), materialId, nodeIds.toArray(), nodeIds.size());
|
||||
Object[] params = new Object[1 + nodeIds.size() + 1];
|
||||
params[0] = materialId;
|
||||
for (int i = 0; i < nodeIds.size(); i++) {
|
||||
params[i + 1] = nodeIds.get(i);
|
||||
}
|
||||
params[params.length - 1] = nodeIds.size();
|
||||
|
||||
var periods = jdbcTemplate.query(validityPeriodSql, new ValidityPeriodMapper(), params);
|
||||
|
||||
if (periods.isEmpty()) {
|
||||
return Optional.empty();
|
||||
|
|
|
|||
|
|
@ -196,8 +196,8 @@ public class CalculationExecutionService {
|
|||
.add(destinationCalculationJob.getAnnualDisposalCost())
|
||||
.add(destinationCalculationJob.getAnnualCapitalCost())
|
||||
.add(destinationCalculationJob.getAnnualStorageCost())
|
||||
.add(materialCost)
|
||||
.add(fcaFee);
|
||||
.add(materialCost.multiply(BigDecimal.valueOf(destination.getAnnualAmount())))
|
||||
.add(fcaFee.multiply(BigDecimal.valueOf(destination.getAnnualAmount())));
|
||||
|
||||
var totalCost = commonCost.add(destinationCalculationJob.getAnnualTransportationCost()).add(destinationCalculationJob.getAnnualCustomCost());
|
||||
var totalRiskCost = commonCost.add(customCost.getAnnualRiskCost()).add(sections.stream().map(SectionInfo::result).map(CalculationJobRouteSection::getAnnualRiskCost).reduce(BigDecimal.ZERO, BigDecimal::add));
|
||||
|
|
|
|||
|
|
@ -77,6 +77,9 @@ public class AirfreightCalculationService {
|
|||
|
||||
private double getAirfreightShare(double maxAirfreightShare, double overseaShare) {
|
||||
|
||||
maxAirfreightShare = maxAirfreightShare*100;
|
||||
overseaShare = overseaShare*100;
|
||||
|
||||
// Ensure oversea share is within valid range
|
||||
if (overseaShare < 0 || overseaShare > 100) {
|
||||
throw new IllegalArgumentException("Oversea share must be between 0 and 100");
|
||||
|
|
@ -87,14 +90,14 @@ public class AirfreightCalculationService {
|
|||
// Linear interpolation: y = mx + b
|
||||
// m = (0.2 * maxAirfreightShare - 0) / (50 - 0) = 0.004 * maxAirfreightShare
|
||||
// b = 0
|
||||
return (0.004 * maxAirfreightShare * overseaShare);
|
||||
return (0.004 * maxAirfreightShare * overseaShare)/100;
|
||||
}
|
||||
// Second segment: from (50, 20% of max) to (100, max)
|
||||
else {
|
||||
// Linear interpolation: y = mx + b
|
||||
// m = (maxAirfreightShare - 0.2 * maxAirfreightShare) / (100 - 50) = 0.016 * maxAirfreightShare
|
||||
// b = 0.2 * maxAirfreightShare - 50 * 0.016 * maxAirfreightShare = -0.6 * maxAirfreightShare
|
||||
return (0.016 * maxAirfreightShare * overseaShare - 0.6 * maxAirfreightShare);
|
||||
return (0.016 * maxAirfreightShare * overseaShare - 0.6 * maxAirfreightShare)/100;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package de.avatic.lcc.service.calculation.execution.steps;
|
||||
|
||||
import de.avatic.lcc.calculationmodel.ContainerCalculationResult;
|
||||
import de.avatic.lcc.calculationmodel.CustomResult;
|
||||
import de.avatic.lcc.calculationmodel.SectionInfo;
|
||||
import de.avatic.lcc.model.premises.Premise;
|
||||
import de.avatic.lcc.model.premises.route.Destination;
|
||||
import de.avatic.lcc.model.properties.CountryPropertyMappingId;
|
||||
|
|
@ -10,10 +12,10 @@ import de.avatic.lcc.repositories.country.CountryPropertyRepository;
|
|||
import de.avatic.lcc.repositories.premise.RouteNodeRepository;
|
||||
import de.avatic.lcc.repositories.properties.PropertyRepository;
|
||||
import de.avatic.lcc.service.CustomApiService;
|
||||
import de.avatic.lcc.calculationmodel.SectionInfo;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -34,6 +36,17 @@ public class CustomCostCalculationService {
|
|||
this.shippingFrequencyCalculationService = shippingFrequencyCalculationService;
|
||||
}
|
||||
|
||||
private BigDecimal getContainerShare(Premise premise, ContainerCalculationResult containerCalculationResult) {
|
||||
var weightExceeded = containerCalculationResult.isWeightExceeded();
|
||||
var mixable = premise.getHuMixable();
|
||||
|
||||
if (mixable) {
|
||||
return BigDecimal.valueOf(weightExceeded ? containerCalculationResult.getHuUtilizationByWeight() : containerCalculationResult.getHuUtilizationByVolume());
|
||||
} else {
|
||||
return BigDecimal.ONE.divide(BigDecimal.valueOf(containerCalculationResult.getHuUnitCount()), 10, RoundingMode.HALF_UP);
|
||||
}
|
||||
}
|
||||
|
||||
public CustomResult doD2dCalculation(Premise premise, Destination destination, List<SectionInfo> sections) {
|
||||
|
||||
var destUnion = countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.UNION, destination.getCountryId()).orElseThrow();
|
||||
|
|
@ -42,7 +55,9 @@ public class CustomCostCalculationService {
|
|||
if (!CustomUnionType.EU.name().equals(destUnion.getCurrentValue()) || !CustomUnionType.NONE.name().equals(sourceUnion.getCurrentValue()))
|
||||
return CustomResult.EMPTY;
|
||||
|
||||
return getCustomCalculationResult(premise, destination, sections.getFirst().result().getAnnualCost(), sections.getFirst().result().getAnnualChanceCost(), sections.getFirst().result().getAnnualRiskCost());
|
||||
double huAnnualAmount = BigDecimal.valueOf(destination.getAnnualAmount()).divide(BigDecimal.valueOf(sections.getFirst().containerResult().getHuUnitCount()),2, RoundingMode.HALF_UP).doubleValue();
|
||||
|
||||
return getCustomCalculationResult(premise, destination, getContainerShare(premise, sections.getFirst().containerResult()), huAnnualAmount, sections.getFirst().result().getAnnualCost(), sections.getFirst().result().getAnnualChanceCost(), sections.getFirst().result().getAnnualRiskCost());
|
||||
}
|
||||
|
||||
public CustomResult doCalculation(Premise premise, Destination destination, List<SectionInfo> sections) {
|
||||
|
|
@ -58,19 +73,22 @@ public class CustomCostCalculationService {
|
|||
var transportationChanceCost = relevantSections.stream().map(s -> s.result().getAnnualChanceCost()).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
var transportationRiskCost = relevantSections.stream().map(s -> s.result().getAnnualRiskCost()).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
return getCustomCalculationResult(premise, destination, transportationCost, transportationChanceCost, transportationRiskCost);
|
||||
|
||||
double huAnnualAmount = BigDecimal.valueOf(destination.getAnnualAmount()).divide(BigDecimal.valueOf(relevantSections.getFirst().containerResult().getHuUnitCount()),2, RoundingMode.HALF_UP).doubleValue();
|
||||
|
||||
return getCustomCalculationResult(premise, destination, getContainerShare(premise, relevantSections.getFirst().containerResult()), huAnnualAmount, transportationCost, transportationChanceCost, transportationRiskCost);
|
||||
}
|
||||
|
||||
return CustomResult.EMPTY;
|
||||
}
|
||||
|
||||
private CustomResult getCustomCalculationResult(Premise premise, Destination destination, BigDecimal transportationCost, BigDecimal transportationChanceCost, BigDecimal transportationRiskCost) {
|
||||
var shippingFrequency = shippingFrequencyCalculationService.doCalculation(destination.getAnnualAmount());
|
||||
private CustomResult getCustomCalculationResult(Premise premise, Destination destination, BigDecimal containerShare, double huAnnualAmount, BigDecimal transportationCost, BigDecimal transportationChanceCost, BigDecimal transportationRiskCost) {
|
||||
var shippingFrequency = shippingFrequencyCalculationService.doCalculation(huAnnualAmount);
|
||||
var customFee = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.CUSTOM_FEE).orElseThrow().getCurrentValue());
|
||||
|
||||
var tariffRate = premise.getTariffRate() == null ? customApiService.getTariffRate(premise.getHsCode(), premise.getCountryId()) : premise.getTariffRate();
|
||||
var tariffRate = premise.getTariffRate() == null ? customApiService.getTariffRate(premise.getHsCode(), premise.getCountryId()) : premise.getTariffRate();
|
||||
|
||||
var materialCost = premise.getMaterialCost();
|
||||
var materialCost = premise.getMaterialCost().multiply(BigDecimal.valueOf(destination.getAnnualAmount()));
|
||||
var fcaFee = BigDecimal.ZERO;
|
||||
|
||||
if (premise.getFcaEnabled()) {
|
||||
|
|
@ -81,16 +99,16 @@ public class CustomCostCalculationService {
|
|||
|
||||
var customValue = materialCost.add(fcaFee).add(transportationCost);
|
||||
var customDuties = customValue.multiply(tariffRate);
|
||||
var annualCustomFee = shippingFrequency * customFee;
|
||||
var annualCost = customDuties.add(BigDecimal.valueOf(annualCustomFee));
|
||||
var annualCustomFee = BigDecimal.valueOf(shippingFrequency).multiply(BigDecimal.valueOf(customFee)).multiply(containerShare);
|
||||
var annualCost = customDuties.add(annualCustomFee);
|
||||
|
||||
var customRiskValue = materialCost.add(fcaFee).add(transportationRiskCost);
|
||||
var customRiskDuties = customRiskValue.multiply(tariffRate);
|
||||
var annualRiskCost = customRiskDuties.add(BigDecimal.valueOf(annualCustomFee));
|
||||
var annualRiskCost = customRiskDuties.add(annualCustomFee);
|
||||
|
||||
var customChanceValue = materialCost.add(fcaFee).add(transportationChanceCost);
|
||||
var customChanceDuties = customChanceValue.multiply(tariffRate);
|
||||
var annualChanceCost = customChanceDuties.add(BigDecimal.valueOf(annualCustomFee));
|
||||
var annualChanceCost = customChanceDuties.add(annualCustomFee);
|
||||
|
||||
return new CustomResult(customValue, customRiskValue, customChanceValue, customDuties, tariffRate, annualCost, annualRiskCost, annualChanceCost);
|
||||
}
|
||||
|
|
@ -116,7 +134,7 @@ public class CustomCostCalculationService {
|
|||
|
||||
private CustomUnionType getCustomUnionByCountryId(Integer countryId) {
|
||||
var property = countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.UNION, countryId).orElseThrow();
|
||||
if(property.getCurrentValue() == null)
|
||||
if (property.getCurrentValue() == null)
|
||||
return CustomUnionType.NONE;
|
||||
|
||||
return CustomUnionType.valueOf(property.getCurrentValue().toUpperCase());
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import de.avatic.lcc.repositories.properties.PropertyRepository;
|
|||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
@Service
|
||||
public class HandlingCostCalculationService {
|
||||
|
|
@ -66,7 +67,7 @@ public class HandlingCostCalculationService {
|
|||
|
||||
|
||||
private HandlingResult getLLCCost(Destination destination, PackagingDimension hu, LoadCarrierType type, boolean addRepackingCosts) {
|
||||
int huAnnualAmount = destination.getAnnualAmount() / hu.getContentUnitCount();
|
||||
double huAnnualAmount = BigDecimal.valueOf(destination.getAnnualAmount()).divide(BigDecimal.valueOf(hu.getContentUnitCount()),2, RoundingMode.UP ).doubleValue();
|
||||
double handling = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.GLT_HANDLING).orElseThrow().getCurrentValue());
|
||||
double release = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.GLT_RELEASE).orElseThrow().getCurrentValue());
|
||||
double dispatch = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.GLT_DISPATCH).orElseThrow().getCurrentValue());
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ public class InventoryCostCalculationService {
|
|||
|
||||
public InventoryCostResult doCalculation(Premise premise, Destination destination, BigDecimal leadTime) {
|
||||
|
||||
|
||||
var fcaFee = BigDecimal.ZERO;
|
||||
|
||||
if (premise.getFcaEnabled()) {
|
||||
|
|
@ -41,6 +42,7 @@ public class InventoryCostCalculationService {
|
|||
}
|
||||
|
||||
var hu = premiseToHuService.createHuFromPremise(premise);
|
||||
double huAnnualAmount = BigDecimal.valueOf(destination.getAnnualAmount()).divide(BigDecimal.valueOf(hu.getContentUnitCount()),0, RoundingMode.UP ).doubleValue();
|
||||
|
||||
var safetydays = BigDecimal.valueOf(Integer.parseInt(countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.SAFETY_STOCK, premise.getCountryId()).orElseThrow().getCurrentValue()));
|
||||
var workdays = BigDecimal.valueOf(Integer.parseInt(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.WORKDAYS).orElseThrow().getCurrentValue()));
|
||||
|
|
@ -51,7 +53,7 @@ public class InventoryCostCalculationService {
|
|||
var dailyAmount = annualAmount.divide(BigDecimal.valueOf(365), 10, RoundingMode.HALF_UP);
|
||||
var workdayAmount = annualAmount.divide(workdays, 10, RoundingMode.HALF_UP);
|
||||
|
||||
var opStock = (annualAmount.divide(BigDecimal.valueOf(Math.max(shippingFrequencyCalculationService.doCalculation(destination.getAnnualAmount()),1)), 10, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(.5)));
|
||||
var opStock = (annualAmount.divide(BigDecimal.valueOf(Math.max(shippingFrequencyCalculationService.doCalculation(huAnnualAmount),1)), 10, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(.5)));
|
||||
var safetyStock = safetydays.multiply(workdayAmount);
|
||||
var stockedInventory = opStock.add(safetyStock);
|
||||
var inTransportStock = dailyAmount.multiply(leadTime);
|
||||
|
|
|
|||
|
|
@ -79,9 +79,11 @@ public class RouteSectionCostCalculationService {
|
|||
PriceCalculationResult prices = calculatePrices(
|
||||
premise.getHuMixable(),
|
||||
rate,
|
||||
containerCalculation.isWeightExceeded(),
|
||||
ContainerType.FEU,
|
||||
containerCalculation.getMaxContainerWeight(),
|
||||
BigDecimal.valueOf(containerCalculation.getTotalUtilizationByVolume()),
|
||||
BigDecimal.valueOf(containerCalculation.getHuUtilizationByWeight()),
|
||||
utilization);
|
||||
|
||||
result.setCbmPrice(!containerCalculation.isWeightExceeded());
|
||||
|
|
@ -143,24 +145,26 @@ public class RouteSectionCostCalculationService {
|
|||
result.setTransitTime(transitTime);
|
||||
|
||||
// Calculate price and annual cost
|
||||
int huAnnualAmount = destination.getAnnualAmount() / containerCalculation.getHu().getContentUnitCount();
|
||||
BigDecimal huAnnualAmount = BigDecimal.valueOf(destination.getAnnualAmount()).divide(BigDecimal.valueOf(containerCalculation.getHu().getContentUnitCount()), 2, RoundingMode.HALF_UP);
|
||||
BigDecimal utilization = getUtilization(section.getRateType());
|
||||
BigDecimal annualVolume = BigDecimal.valueOf(huAnnualAmount * containerCalculation.getHu().getVolume(DimensionUnit.M));
|
||||
BigDecimal annualWeight = BigDecimal.valueOf(huAnnualAmount * containerCalculation.getHu().getWeight(WeightUnit.KG));
|
||||
BigDecimal annualVolume = huAnnualAmount.multiply(BigDecimal.valueOf(containerCalculation.getHu().getVolume(DimensionUnit.M)));
|
||||
BigDecimal annualWeight = huAnnualAmount.multiply(BigDecimal.valueOf(containerCalculation.getHu().getWeight(WeightUnit.KG)));
|
||||
|
||||
PriceCalculationResult prices = calculatePrices(
|
||||
premise.getHuMixable(),
|
||||
rate,
|
||||
containerCalculation.isWeightExceeded(),
|
||||
containerCalculation.getContainerType(),
|
||||
containerCalculation.getMaxContainerWeight(),
|
||||
BigDecimal.valueOf(containerCalculation.getTotalUtilizationByVolume()),
|
||||
BigDecimal.valueOf(containerCalculation.getTotalUtilizationByWeight()),
|
||||
utilization);
|
||||
|
||||
result.setCbmPrice(!containerCalculation.isWeightExceeded());
|
||||
result.setWeightPrice(containerCalculation.isWeightExceeded());
|
||||
result.setCbmPrice(prices.volumePrice);
|
||||
result.setWeightPrice(prices.weightPrice);
|
||||
result.setUtilization(prices.utilization);
|
||||
result.setUtilization(!containerCalculation.isWeightExceeded() || !premise.getHuMixable() ? prices.utilization : BigDecimal.valueOf(1.0));
|
||||
|
||||
var chanceRiskFactors = changeRiskFactorCalculationService.getChanceRiskFactors();
|
||||
|
||||
|
|
@ -180,11 +184,14 @@ public class RouteSectionCostCalculationService {
|
|||
private PriceCalculationResult calculatePrices(
|
||||
boolean huMixable,
|
||||
BigDecimal rate,
|
||||
boolean weightExceeded,
|
||||
ContainerType containerType,
|
||||
int maxContainerWeight,
|
||||
BigDecimal totalVolumeUtilization,
|
||||
BigDecimal totalWeightUtilization,
|
||||
BigDecimal propertyUtilization) {
|
||||
|
||||
|
||||
BigDecimal utilization;
|
||||
BigDecimal volumePrice;
|
||||
BigDecimal weightPrice;
|
||||
|
|
@ -194,12 +201,12 @@ public class RouteSectionCostCalculationService {
|
|||
|
||||
if (huMixable) {
|
||||
volumePrice = cbmRate.divide(propertyUtilization, 10, RoundingMode.HALF_UP);
|
||||
weightPrice = weightRate.divide(propertyUtilization, 10, RoundingMode.HALF_UP);
|
||||
utilization = propertyUtilization;
|
||||
weightPrice = weightRate.divide(BigDecimal.valueOf(1), 10, RoundingMode.HALF_UP);
|
||||
utilization = weightExceeded ? BigDecimal.ONE : propertyUtilization;
|
||||
} else {
|
||||
volumePrice = cbmRate.divide(totalVolumeUtilization, 10, RoundingMode.HALF_UP);
|
||||
weightPrice = weightRate.divide(totalVolumeUtilization, 10, RoundingMode.HALF_UP);
|
||||
utilization = totalVolumeUtilization;
|
||||
weightPrice = weightRate.divide(totalWeightUtilization, 10, RoundingMode.HALF_UP);
|
||||
utilization = weightExceeded ? totalWeightUtilization : totalVolumeUtilization;
|
||||
}
|
||||
|
||||
return new PriceCalculationResult(volumePrice, weightPrice, utilization);
|
||||
|
|
|
|||
|
|
@ -24,4 +24,15 @@ public class ShippingFrequencyCalculationService {
|
|||
|
||||
}
|
||||
|
||||
public double doCalculation(double huAnnualAmount) {
|
||||
Integer minAnnualFrequency = Integer.parseInt(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.FREQ_MIN).orElseThrow().getCurrentValue());
|
||||
Integer maxAnnualFrequency = Integer.parseInt(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.FREQ_MAX).orElseThrow().getCurrentValue());
|
||||
|
||||
if (huAnnualAmount > maxAnnualFrequency.doubleValue())
|
||||
return maxAnnualFrequency;
|
||||
|
||||
return Math.max(huAnnualAmount, minAnnualFrequency.doubleValue());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package de.avatic.lcc.service.transformer.report;
|
||||
|
||||
import de.avatic.lcc.dto.generic.ContainerType;
|
||||
import de.avatic.lcc.dto.generic.NodeType;
|
||||
import de.avatic.lcc.dto.report.ReportDTO;
|
||||
import de.avatic.lcc.dto.report.ReportDestinationDTO;
|
||||
|
|
@ -50,13 +49,15 @@ public class ReportTransformer {
|
|||
List<CalculationJobDestination> destinations = calculationJobDestinationRepository.getDestinationsByJobId(job.getId());
|
||||
Map<Integer, List<CalculationJobRouteSection>> sections = calculationJobRouteSectionRepository.getRouteSectionsByDestinationIds(destinations.stream().map(CalculationJobDestination::getId).toList());
|
||||
|
||||
var weightedTotalCost = getWeightedTotalCosts(sections);
|
||||
|
||||
Premise premise = premiseRepository.getPremiseById(job.getPremiseId()).orElseThrow();
|
||||
|
||||
reportDTO.setCost(getCostMap(job, destinations));
|
||||
reportDTO.setRisk(getRisk(job, destinations));
|
||||
reportDTO.setCost(getCostMap(job, destinations, weightedTotalCost));
|
||||
reportDTO.setRisk(getRisk(job, destinations, weightedTotalCost));
|
||||
reportDTO.setDestination(destinations.stream().map(d -> getDestinationDTO(d, sections.get(d.getId()), premise)).toList());
|
||||
|
||||
if(!reportDTO.getDestinations().isEmpty()) {
|
||||
if (!reportDTO.getDestinations().isEmpty()) {
|
||||
var source = reportDTO.getDestinations().getFirst().getSections().stream().map(ReportSectionDTO::getFromNode).filter(n -> n.getTypes().contains(NodeType.SOURCE)).findFirst().orElseThrow();
|
||||
reportDTO.setSupplier(source);
|
||||
}
|
||||
|
|
@ -65,9 +66,35 @@ public class ReportTransformer {
|
|||
|
||||
}
|
||||
|
||||
private WeightedTotalCosts getWeightedTotalCosts(Map<Integer, List<CalculationJobRouteSection>> sectionsMap) {
|
||||
|
||||
BigDecimal totalPreRunCost = BigDecimal.ZERO;
|
||||
BigDecimal totalMainRunCost = BigDecimal.ZERO;
|
||||
BigDecimal totalPostRunCost = BigDecimal.ZERO;
|
||||
|
||||
BigDecimal totalCost = BigDecimal.ZERO;
|
||||
|
||||
for (List<CalculationJobRouteSection> sections : sectionsMap.values()) {
|
||||
for (CalculationJobRouteSection section : sections) {
|
||||
|
||||
if (section.getPreRun())
|
||||
totalPreRunCost = totalPreRunCost.add(section.getAnnualCost());
|
||||
if(section.getMainRun())
|
||||
totalMainRunCost = totalMainRunCost.add(section.getAnnualCost());
|
||||
if(section.getPostRun())
|
||||
totalPostRunCost = totalPostRunCost.add(section.getAnnualCost());
|
||||
|
||||
totalCost = totalCost.add(section.getAnnualCost());
|
||||
}
|
||||
}
|
||||
|
||||
return new WeightedTotalCosts(totalPreRunCost, totalMainRunCost, totalPostRunCost, totalCost);
|
||||
}
|
||||
|
||||
private ReportDestinationDTO getDestinationDTO(CalculationJobDestination destination, List<CalculationJobRouteSection> sections, Premise premise) {
|
||||
var destinationNode = nodeRepository.getByDestinationId(destination.getPremiseDestinationId()).orElseThrow();
|
||||
|
||||
|
||||
var dimensionUnit = premise.getHuDisplayedDimensionUnit();
|
||||
var weightUnit = premise.getHuDisplayedWeightUnit();
|
||||
|
||||
|
|
@ -76,23 +103,26 @@ public class ReportTransformer {
|
|||
|
||||
var totalAnnualCost = sections.stream().map(CalculationJobRouteSection::getAnnualCost).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
destinationDTO.getSections().forEach(s -> {
|
||||
s.getCost().setPercentage(s.getCost().getTotal().doubleValue()/totalAnnualCost.doubleValue());
|
||||
s.getCost().setPercentage(s.getCost().getTotal().doubleValue() / totalAnnualCost.doubleValue());
|
||||
});
|
||||
destinationDTO.getSections().forEach(s -> {
|
||||
s.getDuration().setPercentage(s.getDuration().getTotal().doubleValue()/totalAnnualCost.doubleValue());
|
||||
s.getDuration().setPercentage(s.getDuration().getTotal().doubleValue() / totalAnnualCost.doubleValue());
|
||||
});
|
||||
|
||||
destinationDTO.setId(destination.getId());
|
||||
destinationDTO.setAnnualQuantity(destination.getAnnualAmount().intValue());
|
||||
|
||||
destinationDTO.setDestination(nodeTransformer.toNodeDTO(destinationNode));
|
||||
|
||||
destinationDTO.setDimensionUnit(dimensionUnit);
|
||||
destinationDTO.setWeightUnit(weightUnit);
|
||||
destinationDTO.setHeight(dimensionUnit.convertFromMM(premise.getIndividualHuHeight()).doubleValue());
|
||||
destinationDTO.setWidth(dimensionUnit.convertFromMM(premise.getIndividualHuWidth()).doubleValue());
|
||||
destinationDTO.setLength(dimensionUnit.convertFromMM(premise.getIndividualHuLength()).doubleValue());
|
||||
destinationDTO.setWeight(weightUnit.convertFromG(premise.getIndividualHuWeight()).doubleValue());
|
||||
destinationDTO.setHeight(dimensionUnit.convertFromMM(premise.getIndividualHuHeight()));
|
||||
destinationDTO.setWidth(dimensionUnit.convertFromMM(premise.getIndividualHuWidth()));
|
||||
destinationDTO.setLength(dimensionUnit.convertFromMM(premise.getIndividualHuLength()));
|
||||
destinationDTO.setWeight(weightUnit.convertFromG(premise.getIndividualHuWeight()));
|
||||
destinationDTO.setHuUnitCount(premise.getHuUnitCount());
|
||||
|
||||
destinationDTO.setLayer(destination.getLayerCount());
|
||||
destinationDTO.setUnitCount(premise.getHuUnitCount());
|
||||
|
||||
destinationDTO.setOverseaShare(premise.getOverseaShare().doubleValue());
|
||||
destinationDTO.setSafetyStock(destination.getSafetyStock().doubleValue());
|
||||
|
|
@ -105,10 +135,11 @@ public class ReportTransformer {
|
|||
destinationDTO.setRate(mainRun == null ? 0 : mainRun.getRate());
|
||||
destinationDTO.setType(destination.getContainerType());
|
||||
destinationDTO.setUtilization(destination.getContainerUtilization());
|
||||
destinationDTO.setUnitCount(premise.getHuUnitCount());
|
||||
destinationDTO.setUnitCount(destination.getHuCount());
|
||||
destinationDTO.setWeightExceeded(destination.getTransportWeightExceeded());
|
||||
|
||||
destinationDTO.setHsCode(premise.getHsCode());
|
||||
destinationDTO.setTariffRate(destination.getTariffRate());
|
||||
|
||||
return destinationDTO;
|
||||
}
|
||||
|
|
@ -133,85 +164,124 @@ public class ReportTransformer {
|
|||
}
|
||||
|
||||
|
||||
private Map<String, ReportEntryDTO> getRisk(CalculationJob job, List<CalculationJobDestination> destination) {
|
||||
private Map<String, ReportEntryDTO> getRisk(CalculationJob job, List<CalculationJobDestination> destination, WeightedTotalCosts weightedTotalCost) {
|
||||
Map<String, ReportEntryDTO> risk = new HashMap<>();
|
||||
|
||||
var totalValue = BigDecimal.valueOf(1); // job.getWeightedTotalCosts(); TODO since this is not stored in table, needs to be calculated
|
||||
var annualAmount = destination.stream().map(CalculationJobDestination::getAnnualAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
|
||||
var airfreightValue = destination.stream().map(CalculationJobDestination::getAnnualAirFreightCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
|
||||
var worstValue = destination.stream().map(CalculationJobDestination::getTotalRiskCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
|
||||
var bestValue = destination.stream().map(CalculationJobDestination::getTotalChanceCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
|
||||
|
||||
|
||||
var totalValue = annualAmount.equals(BigDecimal.ZERO) ? BigDecimal.ZERO : destination.stream().map(CalculationJobDestination::getTotalCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
|
||||
//var totalValue = weightedTotalCost.totalCost.divide(annualAmount, 2, RoundingMode.HALF_UP);
|
||||
|
||||
ReportEntryDTO airfreight = new ReportEntryDTO();
|
||||
var airfreightValue = destination.stream().map(CalculationJobDestination::getAnnualAirFreightCost).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
airfreight.setTotal(airfreightValue);
|
||||
airfreight.setPercentage(airfreightValue.divide(totalValue, 2, RoundingMode.HALF_UP));
|
||||
airfreight.setPercentage(airfreightValue.divide(totalValue, 4, RoundingMode.HALF_UP));
|
||||
risk.put("air_freight_cost", airfreight);
|
||||
|
||||
ReportEntryDTO worst = new ReportEntryDTO();
|
||||
var worstValue = destination.stream().map(CalculationJobDestination::getTotalRiskCost).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
worst.setTotal(worstValue);
|
||||
worst.setPercentage(worstValue.divide(totalValue, 2, RoundingMode.HALF_UP));
|
||||
worst.setPercentage(worstValue.divide(totalValue, 4, RoundingMode.HALF_UP));
|
||||
risk.put("worst_case_cost", worst);
|
||||
|
||||
ReportEntryDTO best = new ReportEntryDTO();
|
||||
var bestValue = destination.stream().map(CalculationJobDestination::getTotalChanceCost).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
best.setTotal(bestValue);
|
||||
best.setPercentage(bestValue.divide(totalValue, 2, RoundingMode.HALF_UP));
|
||||
best.setPercentage(bestValue.divide(totalValue, 4, RoundingMode.HALF_UP));
|
||||
risk.put("best_case_cost", best);
|
||||
|
||||
return risk;
|
||||
}
|
||||
|
||||
private Map<String, ReportEntryDTO> getCostMap(CalculationJob job, List<CalculationJobDestination> destination) {
|
||||
private Map<String, ReportEntryDTO> getCostMap(CalculationJob job, List<CalculationJobDestination> destination, WeightedTotalCosts weightedTotalCost) {
|
||||
Map<String, ReportEntryDTO> cost = new HashMap<>();
|
||||
|
||||
var annualAmount = destination.stream().map(CalculationJobDestination::getAnnualAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
var materialValue = destination.stream().map(CalculationJobDestination::getMaterialCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(BigDecimal.valueOf(destination.size()), 4, RoundingMode.HALF_UP);
|
||||
var fcaFeesValues = destination.stream().map(CalculationJobDestination::getFcaCost).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
var repackingValues = annualAmount.equals(BigDecimal.ZERO) ? BigDecimal.ZERO : destination.stream().map(CalculationJobDestination::getAnnualRepackingCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
|
||||
var handlingValues = annualAmount.equals(BigDecimal.ZERO) ? BigDecimal.ZERO : destination.stream().map(CalculationJobDestination::getAnnualHandlingCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
|
||||
var storageValues = annualAmount.equals(BigDecimal.ZERO) ? BigDecimal.ZERO : destination.stream().map(CalculationJobDestination::getAnnualStorageCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
|
||||
var capitalValues = annualAmount.equals(BigDecimal.ZERO) ? BigDecimal.ZERO : destination.stream().map(CalculationJobDestination::getAnnualCapitalCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
|
||||
var disposalValues = annualAmount.equals(BigDecimal.ZERO) ? BigDecimal.ZERO : destination.stream().map(CalculationJobDestination::getAnnualDisposalCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
|
||||
var customValues = annualAmount.equals(BigDecimal.ZERO) ? BigDecimal.ZERO : destination.stream().map(CalculationJobDestination::getAnnualCustomCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
|
||||
|
||||
var preRunValues = weightedTotalCost.totalPreRunCost.divide(annualAmount, 4, RoundingMode.HALF_UP);
|
||||
var mainRunValues = weightedTotalCost.totalMainRunCost.divide(annualAmount, 4, RoundingMode.HALF_UP);
|
||||
var postRunValues = weightedTotalCost.totalPostRunCost.divide(annualAmount, 4, RoundingMode.HALF_UP);
|
||||
|
||||
|
||||
var totalValue = annualAmount.equals(BigDecimal.ZERO) ? BigDecimal.ZERO : destination.stream().map(CalculationJobDestination::getTotalCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
|
||||
|
||||
ReportEntryDTO total = new ReportEntryDTO();
|
||||
var totalValue = BigDecimal.valueOf(1); // job.getWeightedTotalCosts(); TODO since this is not stored in table, needs to be calculated
|
||||
total.setTotal(totalValue);
|
||||
total.setPercentage(BigDecimal.valueOf(100));
|
||||
total.setPercentage(BigDecimal.valueOf(1));
|
||||
cost.put("total", total);
|
||||
|
||||
ReportEntryDTO preRun = new ReportEntryDTO();
|
||||
preRun.setTotal(preRunValues);
|
||||
preRun.setPercentage(preRunValues.divide(totalValue, 4, RoundingMode.HALF_UP));
|
||||
cost.put("preRun", preRun);
|
||||
|
||||
ReportEntryDTO mainRun = new ReportEntryDTO();
|
||||
mainRun.setTotal(mainRunValues);
|
||||
mainRun.setPercentage(mainRunValues.divide(totalValue, 4, RoundingMode.HALF_UP));
|
||||
cost.put("mainRun", mainRun);
|
||||
|
||||
ReportEntryDTO postRun = new ReportEntryDTO();
|
||||
postRun.setTotal(postRunValues);
|
||||
postRun.setPercentage(postRunValues.divide(totalValue, 4, RoundingMode.HALF_UP));
|
||||
cost.put("postRun", postRun);
|
||||
|
||||
ReportEntryDTO material = new ReportEntryDTO();
|
||||
var materialValue = destination.stream().map(CalculationJobDestination::getMaterialCost).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
material.setTotal(materialValue);
|
||||
material.setPercentage(materialValue.divide(totalValue, 2, RoundingMode.HALF_UP));
|
||||
material.setPercentage(materialValue.divide(totalValue, 4, RoundingMode.HALF_UP));
|
||||
cost.put("material", material);
|
||||
|
||||
ReportEntryDTO custom = new ReportEntryDTO();
|
||||
custom.setTotal(customValues);
|
||||
custom.setPercentage(customValues.divide(totalValue, 4, RoundingMode.HALF_UP));
|
||||
cost.put("custom", custom);
|
||||
|
||||
ReportEntryDTO fcaFees = new ReportEntryDTO();
|
||||
var fcaFeesValues = destination.stream().map(CalculationJobDestination::getFcaCost).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
fcaFees.setTotal(fcaFeesValues);
|
||||
fcaFees.setPercentage(fcaFeesValues.divide(totalValue, 2, RoundingMode.HALF_UP));
|
||||
fcaFees.setPercentage(fcaFeesValues.divide(totalValue, 4, RoundingMode.HALF_UP));
|
||||
cost.put("fcaFees", fcaFees);
|
||||
|
||||
ReportEntryDTO repacking = new ReportEntryDTO();
|
||||
var repackingValues = destination.stream().map(CalculationJobDestination::getAnnualRepackingCost).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
repacking.setTotal(repackingValues);
|
||||
repacking.setPercentage(repackingValues.divide(totalValue, 2, RoundingMode.HALF_UP));
|
||||
repacking.setPercentage(repackingValues.divide(totalValue, 4, RoundingMode.HALF_UP));
|
||||
cost.put("repacking", repacking);
|
||||
|
||||
ReportEntryDTO handling = new ReportEntryDTO();
|
||||
var handlingValues = destination.stream().map(CalculationJobDestination::getAnnualHandlingCost).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
handling.setTotal(handlingValues);
|
||||
handling.setPercentage(handlingValues.divide(totalValue, 2, RoundingMode.HALF_UP));
|
||||
handling.setPercentage(handlingValues.divide(totalValue, 4, RoundingMode.HALF_UP));
|
||||
cost.put("handling", handling);
|
||||
|
||||
ReportEntryDTO storage = new ReportEntryDTO();
|
||||
var storageValues = destination.stream().map(CalculationJobDestination::getAnnualStorageCost).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
storage.setTotal(storageValues);
|
||||
storage.setPercentage(storageValues.divide(totalValue, 2, RoundingMode.HALF_UP));
|
||||
storage.setPercentage(storageValues.divide(totalValue, 4, RoundingMode.HALF_UP));
|
||||
cost.put("storage", storage);
|
||||
|
||||
ReportEntryDTO capital = new ReportEntryDTO();
|
||||
var capitalValues = destination.stream().map(CalculationJobDestination::getAnnualCapitalCost).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
capital.setTotal(capitalValues);
|
||||
capital.setPercentage(capitalValues.divide(totalValue, 2, RoundingMode.HALF_UP));
|
||||
capital.setPercentage(capitalValues.divide(totalValue, 4, RoundingMode.HALF_UP));
|
||||
cost.put("capital", capital);
|
||||
|
||||
ReportEntryDTO disposal = new ReportEntryDTO();
|
||||
var disposalValues = destination.stream().map(CalculationJobDestination::getAnnualDisposalCost).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
disposal.setTotal(disposalValues);
|
||||
disposal.setPercentage(disposalValues.divide(totalValue, 2, RoundingMode.HALF_UP));
|
||||
disposal.setPercentage(disposalValues.divide(totalValue, 4, RoundingMode.HALF_UP));
|
||||
cost.put("disposal", disposal);
|
||||
|
||||
return cost;
|
||||
}
|
||||
|
||||
|
||||
private record WeightedTotalCosts(BigDecimal totalPreRunCost, BigDecimal totalMainRunCost,
|
||||
BigDecimal totalPostRunCost, BigDecimal totalCost) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -516,9 +516,8 @@ CREATE TABLE IF NOT EXISTS calculation_job_destination
|
|||
-- transportation
|
||||
is_d2d BOOLEAN DEFAULT FALSE,
|
||||
rate_d2d DECIMAL(15, 2) DEFAULT NULL,
|
||||
container_type CHAR(8) CHECK (container_type IN
|
||||
('TEU', 'FEU', 'HC', 'TRUCK')),
|
||||
hu_count INT UNSIGNED NOT NULL COMMENT 'number of handling units int total',
|
||||
container_type CHAR(8),
|
||||
hu_count INT UNSIGNED NOT NULL COMMENT 'number of handling units in total (full container, with layers)',
|
||||
layer_structure JSON COMMENT 'json representation of a single layer',
|
||||
layer_count INT UNSIGNED NOT NULL COMMENT 'number of layers per full container or truck',
|
||||
transport_weight_exceeded BOOLEAN DEFAULT FALSE COMMENT 'limiting factor: TRUE if weight limited or FALSE if volume limited',
|
||||
|
|
@ -533,7 +532,8 @@ CREATE TABLE IF NOT EXISTS calculation_job_destination
|
|||
FOREIGN KEY (calculation_job_id) REFERENCES calculation_job (id),
|
||||
FOREIGN KEY (premise_destination_id) REFERENCES premise_destination (id),
|
||||
INDEX idx_calculation_job_id (calculation_job_id),
|
||||
INDEX idx_premise_destination_id (premise_destination_id)
|
||||
INDEX idx_premise_destination_id (premise_destination_id),
|
||||
CONSTRAINT chk_container_type CHECK (container_type IN ('TEU', 'FEU', 'HC', 'TRUCK'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS calculation_job_route_section
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue