From 27b56bc92de62e47c427814cb895d9a5fae4a208 Mon Sep 17 00:00:00 2001 From: Jan Date: Tue, 2 Dec 2025 23:06:28 +0100 Subject: [PATCH] Intermediate commit --- src/frontend/public/apple-touch-icon.png | Bin 4142 -> 4902 bytes src/frontend/public/favicon-96x96.png | Bin 2477 -> 2149 bytes src/frontend/public/favicon.ico | Bin 15086 -> 15086 bytes src/frontend/public/favicon.svg | 8 +- src/frontend/src/App.vue | 8 + src/frontend/src/components/UI/Checkbox.vue | 66 +-- src/frontend/src/components/UI/Modal.vue | 55 ++- src/frontend/src/components/UI/SortButton.vue | 7 +- .../src/components/UI/TabContainer.vue | 11 +- .../layout/bulkedit/BulkEditRow.vue | 117 +++-- .../layout/edit/DestinationListView.vue | 2 - .../edit/destination/DestinationRoute.vue | 2 +- .../edit/destination/mass/DestMassCreate.vue | 221 +++++++++ .../destination/mass/DestMassCreateRow.vue | 110 +++++ .../mass/DestinationMassCreate.vue | 13 - .../destination/mass/DestinationMassEdit.vue | 104 +++- .../mass/DestinationMassHandlingCost.vue | 447 +++++++++++++++++- .../mass/DestinationMassHandlingCostRow.vue | 266 +++++++++++ .../mass/DestinationMassQuantity.vue | 415 +++++++++++++++- .../mass/DestinationMassQuantityRow.vue | 216 +++++++++ .../destination/mass/DestinationMassRoute.vue | 194 +++++++- .../src/pages/CalculationMassEdit.vue | 227 ++++++--- .../src/pages/CalculationSingleEdit.vue | 5 +- src/frontend/src/store/destinationEdit.js | 374 +++------------ src/frontend/src/store/premiseEdit.js | 6 +- src/frontend/src/store/premiseSingleEdit.js | 15 +- .../calculation/PremiseController.java | 8 +- .../destination/DestinationCreateDTO.java | 30 +- .../edit/destination/DestinationSetDTO.java | 6 +- .../premise/DestinationRepository.java | 99 +++- .../service/access/DestinationService.java | 157 +++--- .../service/calculation/RoutingService.java | 4 +- .../CalculationIntegrationTests.java | 11 +- .../DestinationIntegrationTest.java | 8 +- 34 files changed, 2603 insertions(+), 609 deletions(-) create mode 100644 src/frontend/src/components/layout/edit/destination/mass/DestMassCreate.vue create mode 100644 src/frontend/src/components/layout/edit/destination/mass/DestMassCreateRow.vue delete mode 100644 src/frontend/src/components/layout/edit/destination/mass/DestinationMassCreate.vue create mode 100644 src/frontend/src/components/layout/edit/destination/mass/DestinationMassHandlingCostRow.vue create mode 100644 src/frontend/src/components/layout/edit/destination/mass/DestinationMassQuantityRow.vue diff --git a/src/frontend/public/apple-touch-icon.png b/src/frontend/public/apple-touch-icon.png index 92b9fe682916bcda35e8399d458135c735db2afa..d0ce2925e045e625d501f26ed88a8753039edf93 100644 GIT binary patch literal 4902 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 diff --git a/src/frontend/public/favicon-96x96.png b/src/frontend/public/favicon-96x96.png index 27edd5802d7cf0686474c9f539ef696621cf30ae..afb1ea191dcb55b10e0086919c896fd8dd5c4eee 100644 GIT binary patch delta 2137 zcmV-f2&VU~6Xg()BYy}QNkl#u_#z+^<-l~@Svav5mb09NCh7SHE69!+x@(k zUo+Xf-R$nn?93)p{4x7ycIG$V@9+2Bd9xE7y&C^{MPO9Le}7&Phy-|rU#UnyB)~HQ zp1Y435DD;%famUG21EipBjCCFm;sRh4+sR*Wy}E2WJCfY0iF@?+>~`$ zfm{GQ1_{Nri|+z@_w1W$cJk@siUHgWf&lay-8KdO;D3YnsdMkls?OZB+Oumc0C?AT z!7Bmi^Z1Zy<@#=~tpVtO4+(4UhI_>e0Yo}dgU%;8)5@}tAvagQJr%sx=Z&XP0+Muo z4)7QxI(T-POV za6HxcvVVqi0tN${UbB2q2B2&mqP=@|L74(i_K}5ys9*idnrj$!vi-X_cSZmM$QxwU zf!Ne}z{M~8%kzVK)WMQeHuLJ)xbB32GJ`DRJduvn5cy^57u7)mM=JiZh6rSn@wg(w zDgnRz*6XiUB0EK3l)3lXhjCF&X2qG#%q-TI>SjM1L}izHZ+< zNA@ue#>2R{_VMMPDXf`Q0^U1w{`!|GobRSHmVxs-jU1M2KnjuqBDgIXvwP@N!K5 zb&UW)=Vb-8cjZzac)*8*y6vv_oOJ@ELw~?QzllmAf~X>`F_Ubd58Af@y!h17R!~3u z_s3LjK36V*XAGD#0%#mVjv#_kC!(9C`C!my?+5y*YxGYrKmu)u&VO|ICr%qN=LEsczR0J>2*3$s1LIgXBC%uP{h+rw?0=X@ z*L9vcfDZVOz%-103W4vz-mJN4uEJ=aHLd{yd{7i~MqZFBk%3pb*hb-w((H zz{&NxWhZ^P(jM*729eHup2+9iw*^Ol0K#;Th~+&|nTWIzp3`++pH}u9%;(cCYyB52 z0gMK@Ac8v;-Va1N#C6ihrwxo~pMP00pH}YMa=SXV?ID`9W!^toClXji#+xD+rL$A}i>5_Wi&)*9CZVo|z4iFVEI3pS<~Q_5Q6- zr~^0cuG>W6bFCskMk4Qr0q`puuzD7k4*>l5j>pwgx7;J$Iw@O807&qDfPV-|&TTCp zpz}x#k~22Cm8P9qYYEU%nMs$4Z7d%EI^aWs-oDG@4M9N9a9WAnxYYR~0yhK!MFT9l zN2C++M1D~{m-jX$0b`@v`cq24hXj`Zuh;gbBYzT=Sc4ZILzkpO8j%o4zvR-*SI zdAMl^IB?@`Wjd{7s(bXW&ws4)w`mA4L5*M5wx+sSKOzBHM6~ms_RG3wIMvmfU3C}< zSOv$*3F}BR7FJMKS{!~93CQ4La|Jci^w85tz%nf8ysZ_~P5`?i*ie<-qEgv&3lA`@Y(nRYTy2~HafXdr*?$dNa?f>%&d*p{ zK{fT&8v)`tnLtKBh+K13A~B-#JxkL{Q(wIkzyOk*4ZYm6WqdCHd`K{9bMFlSe-nQ6 zfqf=p>%W|lC9)#XwlEqsy8N(d#LEimvF!oxiuiQl<^G>2u|r{b+_ehi2qGwL6H&(@ zmn*2&C5W8~T}wOL$bSTknNuC$OrK%p)b*D#>jcQ4`C!f-9k!ClwK^r>^OD9A%d(W` z4@=j4tReFA>e#yOi~t;D#{xUlAi`9ZAOcw;BU}+7IuH1ez!+ScQv$?!ktN75C4%O0PBLoOQ$Wb?aKTw(wkt|PJrCpN}o##uL>wgVcc|JBufH*-WAWLM1 zTx+^SVvrhi-tDc&<6>l~TTsqhCG+2{3hBnGKLa(wE%?o;+S zI0A4GS>oM*)ql+fKnHwCpv_P%NCE^bh(MP3JAkr8AUC1uyo_g988iXnu)eI3M+A{h z#1r}QTpP+(5CD)&TRU^psx##M0Pq+jP`6Fi8Uh4VWQljfmnY}tQZO5BuRB!XpmhYu z*dEz`uN=0zY%Kw;MqP705}<+Mz;pL810n&Q5%Ao7 z%z#LMX9PTVA2T2l;28nW-Ny`AhXB{NuK@r6|NneZV+;TQ00v1!K~w_(BfJBQ2ZHbd P00000NkvXXu0mjfK4bvA delta 2468 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 i0384T21!IgR09AA_SDBB%Ny$e0000=* diff --git a/src/frontend/public/favicon.ico b/src/frontend/public/favicon.ico index 1b2abbf41290ebaaad6d9bd75d07ee040d5d414f..f29e836378acb4d347cff706ef5a093f8888a7ab 100644 GIT binary patch literal 15086 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 diff --git a/src/frontend/public/favicon.svg b/src/frontend/public/favicon.svg index 0245ba4..13ed733 100644 --- a/src/frontend/public/favicon.svg +++ b/src/frontend/public/favicon.svg @@ -1,4 +1,4 @@ - + - + - \ No newline at end of file + \ No newline at end of file diff --git a/src/frontend/src/App.vue b/src/frontend/src/App.vue index ef68e40..66c8e16 100644 --- a/src/frontend/src/App.vue +++ b/src/frontend/src/App.vue @@ -29,6 +29,14 @@ export default { padding: 0; } + + + + +html.modal-open { + background: #f8fafc; +} + .page-header { font-weight: normal; margin-bottom: 3rem; diff --git a/src/frontend/src/components/UI/Checkbox.vue b/src/frontend/src/components/UI/Checkbox.vue index d1189b6..2463b18 100644 --- a/src/frontend/src/components/UI/Checkbox.vue +++ b/src/frontend/src/components/UI/Checkbox.vue @@ -1,24 +1,24 @@ - \ No newline at end of file diff --git a/src/frontend/src/components/layout/edit/destination/mass/DestMassCreateRow.vue b/src/frontend/src/components/layout/edit/destination/mass/DestMassCreateRow.vue new file mode 100644 index 0000000..e054457 --- /dev/null +++ b/src/frontend/src/components/layout/edit/destination/mass/DestMassCreateRow.vue @@ -0,0 +1,110 @@ + + + + + + \ No newline at end of file diff --git a/src/frontend/src/components/layout/edit/destination/mass/DestinationMassCreate.vue b/src/frontend/src/components/layout/edit/destination/mass/DestinationMassCreate.vue deleted file mode 100644 index e55ab2e..0000000 --- a/src/frontend/src/components/layout/edit/destination/mass/DestinationMassCreate.vue +++ /dev/null @@ -1,13 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/frontend/src/components/layout/edit/destination/mass/DestinationMassEdit.vue b/src/frontend/src/components/layout/edit/destination/mass/DestinationMassEdit.vue index 12648ba..63eece3 100644 --- a/src/frontend/src/components/layout/edit/destination/mass/DestinationMassEdit.vue +++ b/src/frontend/src/components/layout/edit/destination/mass/DestinationMassEdit.vue @@ -1,14 +1,104 @@ - - + + + \ No newline at end of file diff --git a/src/frontend/src/components/layout/edit/destination/mass/DestinationMassHandlingCost.vue b/src/frontend/src/components/layout/edit/destination/mass/DestinationMassHandlingCost.vue index 6805a9a..2736f99 100644 --- a/src/frontend/src/components/layout/edit/destination/mass/DestinationMassHandlingCost.vue +++ b/src/frontend/src/components/layout/edit/destination/mass/DestinationMassHandlingCost.vue @@ -1,15 +1,446 @@ - - \ No newline at end of file diff --git a/src/frontend/src/components/layout/edit/destination/mass/DestinationMassHandlingCostRow.vue b/src/frontend/src/components/layout/edit/destination/mass/DestinationMassHandlingCostRow.vue new file mode 100644 index 0000000..cafaf9c --- /dev/null +++ b/src/frontend/src/components/layout/edit/destination/mass/DestinationMassHandlingCostRow.vue @@ -0,0 +1,266 @@ + + + + + \ No newline at end of file diff --git a/src/frontend/src/components/layout/edit/destination/mass/DestinationMassQuantity.vue b/src/frontend/src/components/layout/edit/destination/mass/DestinationMassQuantity.vue index e206831..ebfcb32 100644 --- a/src/frontend/src/components/layout/edit/destination/mass/DestinationMassQuantity.vue +++ b/src/frontend/src/components/layout/edit/destination/mass/DestinationMassQuantity.vue @@ -1,15 +1,414 @@ - - \ No newline at end of file diff --git a/src/frontend/src/components/layout/edit/destination/mass/DestinationMassQuantityRow.vue b/src/frontend/src/components/layout/edit/destination/mass/DestinationMassQuantityRow.vue new file mode 100644 index 0000000..28a7722 --- /dev/null +++ b/src/frontend/src/components/layout/edit/destination/mass/DestinationMassQuantityRow.vue @@ -0,0 +1,216 @@ + + + + + + \ No newline at end of file diff --git a/src/frontend/src/components/layout/edit/destination/mass/DestinationMassRoute.vue b/src/frontend/src/components/layout/edit/destination/mass/DestinationMassRoute.vue index aa8c9fe..29ccec9 100644 --- a/src/frontend/src/components/layout/edit/destination/mass/DestinationMassRoute.vue +++ b/src/frontend/src/components/layout/edit/destination/mass/DestinationMassRoute.vue @@ -1,14 +1,192 @@ - - \ No newline at end of file + diff --git a/src/frontend/src/pages/CalculationSingleEdit.vue b/src/frontend/src/pages/CalculationSingleEdit.vue index adbab33..f13a3f8 100644 --- a/src/frontend/src/pages/CalculationSingleEdit.vue +++ b/src/frontend/src/pages/CalculationSingleEdit.vue @@ -174,11 +174,10 @@ export default { }, close() { if (this.bulkEditQuery) { - //TODO: deselect and save - // this.premiseEditStore.deselectPremise(); + //TODO: deselect element and save this.$router.push({name: 'bulk', params: {ids: this.bulkEditQuery}}); } else { - //TODO: deselect and save + //TODO: deselect element and save this.$router.push({name: 'home'}); } }, diff --git a/src/frontend/src/store/destinationEdit.js b/src/frontend/src/store/destinationEdit.js index ce60655..896657a 100644 --- a/src/frontend/src/store/destinationEdit.js +++ b/src/frontend/src/store/destinationEdit.js @@ -1,14 +1,53 @@ import {defineStore} from 'pinia' -import {toRaw} from "vue"; +import performRequest from "@/backend.js"; +import {config} from '@/config' export const useDestinationEditStore = defineStore('destinationEdit', { state: () => ({ destinations: null, loading: false, }), - getters: {}, - actions: { + getters: { + checkDestinationAssignment(state) { + return (ids) => { + let some = false; + let all = true; + ids.forEach(id => { + const dest = state.destinations?.get(id); + + if ((dest ?? null) === null || dest.length === 0) + all = false; + else + some = true; + + }); + + if (all) + return "all"; + else if (some) + return "some"; + else + return "none"; + } + }, + getByPremiseId(state) { + return (id) => { + return state.destinations?.get(id); + } + }, + getByPremiseIds(state) { + return (ids) => { + return new Map( + [...state.destinations].filter(([premiseId, destinations]) => ids.includes(premiseId)) + ); + } + }, + showProcessingModal(state) { + return state.loading; + } + }, + actions: { setupDestinations(premisses) { this.loading = true; @@ -18,312 +57,39 @@ export const useDestinationEditStore = defineStore('destinationEdit', { this.loading = false; }, + async massSetDestinations(updateMatrix) { + this.loading = true; + + const toBeAdded = {}; + const toBeDeletedMap = new Map(); + + updateMatrix.forEach(row => { + toBeAdded[row.id] = row.destinations.filter(d => d.selected).map(d => d.id); + toBeDeletedMap.set(row.id, row.destinations.filter(d => !d.selected).map(d => d.id)); + }); + + const url = `${config.backendUrl}/calculation/destination`; + const { + data: data, + headers: headers + } = await performRequest(this, 'POST', url, {'destination_node_ids': toBeAdded}); + + this.destinations.forEach((destinations, premiseId) => { + const toBeDeleted = toBeDeletedMap.get(premiseId); + + const filtered = destinations !== null ? destinations.filter(d => !toBeDeleted?.includes(d.destination_node.id)) : []; + + this.destinations.set(premiseId, [...filtered, ...data[premiseId]]); + }); - /** - * DESTINATION stuff - * ================= - */ - - prepareDestinations(dataSourcePremiseId, editedPremiseIds, massEdit = false, fromMassEditView = false) { - if (this.premisses === null) return; - if (!editedPremiseIds || !dataSourcePremiseId || editedPremiseIds.length === 0) return; - - this.destinations = { - premise_ids: editedPremiseIds, - massEdit: massEdit, - fromMassEditView: fromMassEditView, - destinations: this.premisses.find(p => String(p.id) === String(dataSourcePremiseId))?.destinations.map(d => this.copyAllFromPremises(d, !massEdit)) ?? [], - }; - - this.selectedDestination = null; - + this.loading = false; }, - async executeDestinationsMassEdit() { + async massUpdateDestinations(updateMatrix) { + this.loading = true; - if (!this.destinations.massEdit) { - - this.destinations.premise_ids.forEach(premiseId => { - const toPremise = this.getById(premiseId); - - this.destinations.destinations.forEach(fromDest => { - const toDest = toPremise.destinations.find(to => fromDest.id.substring(1) === String(to.id)); - - if ((toDest ?? null) === null) { - throw new Error("Destination not found in premise: " + premiseId + " -> " + d.id); - } - - this.copyAllToPremise(fromDest, toDest); - - const body = { - annual_amount: toDest.annual_amount, - repackaging_costs: toDest.repackaging_costs, - handling_costs: toDest.handling_costs, - disposal_costs: toDest.disposal_costs, - is_d2d: toDest.is_d2d, - rate_d2d: toDest.rate_d2d, - lead_time_d2d: toDest.lead_time_d2d, - route_selected_id: toDest.routes.find(r => r.is_selected)?.id ?? null, - }; - - const url = `${config.backendUrl}/calculation/destination/${toDest.id}`; - performRequest(this, 'PUT', url, body, false); - - }); - }); - - - } else { - this.processDestinationMassEdit = true; - - const destinations = []; - - this.destinations.destinations.forEach(d => { - const dest = { - destination_node_id: d.destination_node.id, - annual_amount: d.annual_amount, - disposal_costs: d.userDefinedHandlingCosts ? d.disposal_costs : null, - repackaging_costs: d.userDefinedHandlingCosts ? d.repackaging_costs : null, - handling_costs: d.userDefinedHandlingCosts ? d.handling_costs : null, - } - destinations.push(dest); - }) - - const body = {destinations: destinations, premise_id: this.destinations.premise_ids}; - const url = `${config.backendUrl}/calculation/destination/`; - - const {data: data, headers: headers} = await performRequest(this, 'PUT', url, body).catch(e => { - this.destinations = null; - this.processDestinationMassEdit = false; - }); - - if (data) { - for (const id of Object.keys(data)) { - this.premisses.find(p => String(p.id) === id).destinations = data[id]; - } - } - - this.destinations = null; - this.processDestinationMassEdit = false; - } - }, - cancelMassEdit() { - this.destinations = null; + this.loading = false; }, - copyAllFromPremises(from, fullCopy = true) { - - const d = {}; - - d.id = `e${from.id}`; - d.destination_node = structuredClone(toRaw(from.destination_node)); - d.routes = fullCopy ? structuredClone(toRaw(from.routes)) : null; - - d.annual_amount = from.annual_amount; - d.is_d2d = from.is_d2d; - d.rate_d2d = from.is_d2d ? from.rate_d2d : null; - d.lead_time_d2d = from.is_d2d ? from.lead_time_d2d : null; - d.handling_costs = from.handling_costs; - d.disposal_costs = from.disposal_costs; - d.repackaging_costs = from.repackaging_costs; - d.userDefinedHandlingCosts = from.handling_costs !== null || from.disposal_costs !== null || from.repackaging_costs !== null; - - return d; - }, - copyAllToPremise(from, to, fullCopy = true) { - - const d = to ?? {}; - - d.annual_amount = from.annual_amount; - d.is_d2d = from.is_d2d; - d.rate_d2d = from.is_d2d ? from.rate_d2d : null; - d.lead_time_d2d = from.is_d2d ? from.lead_time_d2d : null; - - if (from.userDefinedHandlingCosts) { - d.disposal_costs = from.disposal_costs; - d.repackaging_costs = from.repackaging_costs; - d.handling_costs = from.handling_costs; - } else { - d.disposal_costs = null; - d.repackaging_costs = null; - d.handling_costs = null; - } - - if (fullCopy && (from.routes ?? null) !== null) { - to.routes.forEach(route => route.is_selected = from.routes.find(r => r.id === route.id)?.is_selected ?? false); - } - - return d; - }, - - /** - * Selects all destinations for the given "ids" for editing. - * This creates a copy of the destination with id "id". - * They are written back as soon as the user closes the dialog. - */ - selectDestination(id) { - if (this.premisses === null) return; - - logger.info("selectDestination:", id) - - const dest = this.destinations.destinations.find(d => d.id === id); - - - if ((dest ?? null) == null) { - const error = { - code: 'Frontend error.', - message: `Destination not found: ${id}. Please contact support.`, - trace: null - } - throw new Error("Internal frontend error: Destination not found: " + id); - } - - this.selectedDestination = structuredClone(toRaw(dest)); - }, - async deselectDestinations(save = false) { - if (this.premisses === null) return; - - - if (save) { - const idx = this.destinations.destinations.findIndex(d => d.id === this.selectedDestination.id); - this.destinations.destinations.splice(idx, 1, this.selectedDestination); - - if (!this.destinations.fromMassEditView) { - //TODO write trough backend if no massEdit - - const toDest = this.singleSelectedPremise.destinations.find(to => this.selectedDestination.id.substring(1) === String(to.id)); - this.copyAllToPremise(this.selectedDestination, toDest); - - const body = { - annual_amount: toDest.annual_amount, - repackaging_costs: toDest.repackaging_costs, - handling_costs: toDest.handling_costs, - disposal_costs: toDest.disposal_costs, - is_d2d: toDest.is_d2d, - rate_d2d: toDest.rate_d2d, - lead_time_d2d: toDest.lead_time_d2d, - route_selected_id: toDest.routes.find(r => r.is_selected)?.id ?? null, - }; - - logger.info(body) - - const url = `${config.backendUrl}/calculation/destination/${toDest.id}`; - await performRequest(this, 'PUT', url, body, false); - - } - - } - - this.selectedDestination = null; - }, - async deleteDestination(id) { - - - /* - * 1. delete from destinations copy - */ - const idx = this.destinations.destinations.findIndex(d => d.id === id); - - if (idx === -1) { - logger.info("Destination not found in mass edit: , id)"); - return; - } - - this.destinations.destinations.splice(idx, 1); - - /* - * 2. delete from backend if not mass edit - */ - - if (!this.destinations.massEdit && id.startsWith('e')) { /* 'v'-ids cannot be deleted because they only exist in the frontend */ - if (this.premisses === null) return; - - const origId = id.substring(1); - - const url = `${config.backendUrl}/calculation/destination/${origId}`; - await performRequest(this, 'DELETE', url, null, false).catch(async 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)) - - logger.info(toBeDeleted) - - if (toBeDeleted !== -1) { - p.destinations.splice(toBeDeleted, 1) - break; - } - } - } - }, - async addDestination(node) { - - if (this.destinations.massEdit) { - - const existing = this.destinations.destinations.find(d => d.destination_node.id === node.id); - logger.info(existing) - - if ((existing ?? null) !== null) { - logger.info("Destination already exists", node.id); - return [existing.id]; - } - - const destination = { - id: `v${node.id}`, - destination_node: structuredClone(toRaw(node)), - massEdit: true, - annual_amount: 0, - is_d2d: false, - rate_d2d: null, - lead_time_d2d: null, - disposal_costs: null, - repackaging_costs: null, - handling_costs: null, - userDefinedHandlingCosts: false, - }; - - this.destinations.destinations.push(destination); - - return [destination.id]; - - } else { - const id = node.id; - - this.processDestinationMassEdit = true; - - - const toBeUpdated = this.destinations.fromMassEditView ? this.destinations.premise_ids : this.premisses?.filter(p => this.selectedIds.includes(p.id)).map(p => p.id); - - if (toBeUpdated === null || toBeUpdated.length === 0) return; - - const body = {destination_node_id: id, premise_id: toBeUpdated}; - const url = `${config.backendUrl}/calculation/destination/`; - - - const {data: destinations} = await performRequest(this, 'POST', url, body).catch(e => { - this.loading = false; - this.selectedLoading = false; - this.processDestinationMassEdit = false; - throw e; - }); - - const mappedIds = [] - - for (const id of Object.keys(destinations)) { - const premise = this.premisses.find(p => String(p.id) === id) - premise.destinations.push(destinations[id]); - const mappedDestination = this.copyAllFromPremises(destinations[id], true); - mappedIds.push(mappedDestination.id); - this.destinations.destinations.push(mappedDestination); - } - - this.processDestinationMassEdit = false; - - return mappedIds; - } - }, } }); \ No newline at end of file diff --git a/src/frontend/src/store/premiseEdit.js b/src/frontend/src/store/premiseEdit.js index d440372..02749ab 100644 --- a/src/frontend/src/store/premiseEdit.js +++ b/src/frontend/src/store/premiseEdit.js @@ -1,7 +1,5 @@ import {defineStore} from 'pinia' import {config} from '@/config' -import {toRaw} from "vue"; -import {useNotificationStore} from "@/store/notification.js"; import logger from "@/logger.js" import performRequest from '@/backend.js' @@ -164,7 +162,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', { this.loading = true; - const direction = (type !== this.sortedBy) ? 'desc' : (this.order.get(type) === 'asc' ? 'desc' : 'asc'); + const direction = (type !== this.sortedBy) ? this.order.get(type) : (this.order.get(type) === 'asc' ? 'desc' : 'asc'); const temp = this.premisses.slice(); temp.sort((a, b) => { @@ -179,7 +177,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', { else return a.id - b.id; }); - console.log("sort", this.sortedBy, direction, type); + this.premisses = temp; this.sortedBy = type; this.order.set(type, direction); diff --git a/src/frontend/src/store/premiseSingleEdit.js b/src/frontend/src/store/premiseSingleEdit.js index bc5ba56..1a01135 100644 --- a/src/frontend/src/store/premiseSingleEdit.js +++ b/src/frontend/src/store/premiseSingleEdit.js @@ -90,9 +90,13 @@ export const usePremiseSingleEditStore = defineStore('premiseSingleEdit', { if (this.premise === null) return; this.routing = true; - const body = {destination_node_id: node.id, premise_id: [this.premise.id]}; + const destinationNodeIds = {}; + destinationNodeIds[this.premise.id] = [node.id, ...this.premise.destinations.map(d => d.destination_node.id)]; + + const body = {destination_node_ids: destinationNodeIds}; const url = `${config.backendUrl}/calculation/destination/`; + logger.info("addDestination", body, url); const {data: destinations} = await performRequest(this, 'POST', url, body).catch(e => { this.routing = false; @@ -101,10 +105,11 @@ export const usePremiseSingleEditStore = defineStore('premiseSingleEdit', { const ids = [] - for (const destId of Object.keys(destinations)) { - this.premise.destinations.push(destinations[destId]); - ids.push(destinations[destId].id); - } + if (destinations[this.premise.id]?.length !== 0) + for (const destId of Object.keys(destinations[this.premise.id])) { + this.premise.destinations.push(destinations[this.premise.id][destId]); + ids.push(destinations[this.premise.id][destId].id); + } this.routing = false; diff --git a/src/main/java/de/avatic/lcc/controller/calculation/PremiseController.java b/src/main/java/de/avatic/lcc/controller/calculation/PremiseController.java index 24bc8bd..c1a0eb4 100644 --- a/src/main/java/de/avatic/lcc/controller/calculation/PremiseController.java +++ b/src/main/java/de/avatic/lcc/controller/calculation/PremiseController.java @@ -29,8 +29,6 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; import java.util.Map; @@ -176,14 +174,14 @@ public class PremiseController { @PostMapping({"/destination", "/destination/"}) @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')") - public ResponseEntity> createDestination(@RequestBody @Valid DestinationCreateDTO destinationCreateDTO) { - return ResponseEntity.ok(destinationService.createDestination(destinationCreateDTO)); + public ResponseEntity>> createDestination(@RequestBody @Valid DestinationCreateDTO destinationCreateDTO) { + return ResponseEntity.ok(destinationService.massSetDestinations(destinationCreateDTO)); } @PutMapping({"/destination", "/destination/"}) @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')") public ResponseEntity>> setDestination(@RequestBody DestinationSetDTO destinationSetDTO) { - return ResponseEntity.ok(destinationService.setDestination(destinationSetDTO)); + return ResponseEntity.ok(destinationService.massSetDestinationProperties(destinationSetDTO)); } @GetMapping({"/destination/{id}", "/destination/{id}/"}) diff --git a/src/main/java/de/avatic/lcc/dto/calculation/edit/destination/DestinationCreateDTO.java b/src/main/java/de/avatic/lcc/dto/calculation/edit/destination/DestinationCreateDTO.java index e2fa2b2..7ffff82 100644 --- a/src/main/java/de/avatic/lcc/dto/calculation/edit/destination/DestinationCreateDTO.java +++ b/src/main/java/de/avatic/lcc/dto/calculation/edit/destination/DestinationCreateDTO.java @@ -6,32 +6,20 @@ import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import java.util.List; +import java.util.Map; public class DestinationCreateDTO { - @NotEmpty (message = "At least one premise must be selected") - @NotNull (message = "At least one premise must be selected") - @JsonProperty("premise_id") - List<@Min(value = 1, message = "Invalid premise id") Integer> premiseId; + @NotEmpty(message = "At least one premise must be selected") + @NotNull(message = "At least one premise must be selected") + @JsonProperty("destination_node_ids") + Map> destinationNodeIds; - @Min(value = 1, message = "Invalid destination node id") - @NotNull (message = "Destination node id must be provided") - @JsonProperty("destination_node_id") - Integer destinationNodeId; - - public List getPremiseId() { - return premiseId; + public Map> getDestinationNodeIds() { + return destinationNodeIds; } - public void setPremiseId(List premiseId) { - this.premiseId = premiseId; - } - - public Integer getDestinationNodeId() { - return destinationNodeId; - } - - public void setDestinationNodeId(Integer destinationNodeId) { - this.destinationNodeId = destinationNodeId; + public void setDestinationNodeIds(Map> destinationNodeIds) { + this.destinationNodeIds = destinationNodeIds; } } diff --git a/src/main/java/de/avatic/lcc/dto/calculation/edit/destination/DestinationSetDTO.java b/src/main/java/de/avatic/lcc/dto/calculation/edit/destination/DestinationSetDTO.java index 024ca56..adce6cb 100644 --- a/src/main/java/de/avatic/lcc/dto/calculation/edit/destination/DestinationSetDTO.java +++ b/src/main/java/de/avatic/lcc/dto/calculation/edit/destination/DestinationSetDTO.java @@ -1,11 +1,10 @@ package de.avatic.lcc.dto.calculation.edit.destination; import com.fasterxml.jackson.annotation.JsonProperty; -import jakarta.validation.constraints.DecimalMin; -import jakarta.validation.constraints.Digits; -import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.*; import java.util.List; +import java.util.Map; public class DestinationSetDTO { @@ -15,6 +14,7 @@ public class DestinationSetDTO { @JsonProperty("destinations") List destinations; + public List getPremiseId() { return premiseId; } diff --git a/src/main/java/de/avatic/lcc/repositories/premise/DestinationRepository.java b/src/main/java/de/avatic/lcc/repositories/premise/DestinationRepository.java index cedb55a..3b47aa3 100644 --- a/src/main/java/de/avatic/lcc/repositories/premise/DestinationRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/premise/DestinationRepository.java @@ -17,6 +17,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.*; +import java.util.stream.Collectors; @Service public class DestinationRepository { @@ -50,6 +51,21 @@ public class DestinationRepository { return jdbcTemplate.query(query, new DestinationMapper(), id); } + @Transactional + public List getByPremiseIdAndUserId(Integer premiseId, Integer userId) { + + String premiseCheckQuery = "SELECT COUNT(*) FROM premise WHERE id = ? AND user_id = ?"; + + Integer count = jdbcTemplate.queryForObject(premiseCheckQuery, Integer.class, premiseId, userId); + + if (count == null || count == 0) { + return Collections.emptyList(); + } + + String query = "SELECT * FROM premise_destination WHERE premise_id = ?"; + return jdbcTemplate.query(query, new DestinationMapper(), premiseId); + } + @Transactional public void update(Integer id, Integer annualAmount, BigDecimal repackingCost, BigDecimal disposalCost, BigDecimal handlingCost, Boolean isD2d, BigDecimal d2dRate, BigDecimal d2dLeadTime) { if (id == null) { @@ -73,7 +89,7 @@ public class DestinationRepository { setClauses.add("handling_cost = :handlingCost"); parameters.put("handlingCost", handlingCost); - var setD2d = isD2d != null ? isD2d : false; + var setD2d = isD2d != null ? isD2d : false; setClauses.add("is_d2d = :isD2d"); parameters.put("isD2d", setD2d); @@ -143,10 +159,10 @@ public class DestinationRepository { String placeholders = String.join(",", Collections.nCopies(ids.size(), "?")); String query = String.format(""" - SELECT pd.id AS pd_id, p.user_id AS user_id - FROM premise_destination pd - JOIN premise p ON pd.premise_id = p.id - WHERE pd.id IN (%s)""", placeholders); + SELECT pd.id AS pd_id, p.user_id AS user_id + FROM premise_destination pd + JOIN premise p ON pd.premise_id = p.id + WHERE pd.id IN (%s)""", placeholders); return jdbcTemplate.query(query, rs -> { Map result = new HashMap<>(); @@ -157,20 +173,73 @@ public class DestinationRepository { }, ids.toArray()); } +// @Transactional +// public List getByPremiseIdsAndNodeId(List premiseId, Integer nodeId, Integer userId) { +// String placeholder = String.join(",", Collections.nCopies(premiseId.size(), "?")); +// String query = "SELECT * FROM premise_destination JOIN premise ON premise_destination.premise_id = premise.id WHERE premise_destination.premise_id IN (" + placeholder + ") AND premise_destination.destination_node_id = ? AND premise.user_id = ?"; +// +// // Create array with all parameters +// Object[] params = new Object[premiseId.size() + 2]; +// for (int i = 0; i < premiseId.size(); i++) { +// params[i] = premiseId.get(i); +// } +// params[premiseId.size()] = nodeId; +// params[premiseId.size() + 1] = userId; +// +// return jdbcTemplate.query(query, new DestinationMapper(), params); +// } + @Transactional - public List getByPremiseIdsAndNodeId(List premiseId, Integer nodeId, Integer userId) { - String placeholder = String.join(",", Collections.nCopies(premiseId.size(), "?")); - String query = "SELECT * FROM premise_destination JOIN premise ON premise_destination.premise_id = premise.id WHERE premise_destination.premise_id IN (" + placeholder + ") AND premise_destination.destination_node_id = ? AND premise.user_id = ?"; + public Map> getByPremiseIdsAndNodeIds(Map> premiseToNodes, Integer userId) { + if (premiseToNodes.isEmpty()) { + return new HashMap<>(); + } + + // Flatten all premise IDs and node IDs for the query + List allPremiseIds = new ArrayList<>(premiseToNodes.keySet()); + Set allNodeIds = premiseToNodes.values().stream() + .flatMap(List::stream) + .collect(Collectors.toSet()); + + String premisePlaceholder = String.join(",", Collections.nCopies(allPremiseIds.size(), "?")); + String nodePlaceholder = String.join(",", Collections.nCopies(allNodeIds.size(), "?")); + + String query = "SELECT * FROM premise_destination " + + "JOIN premise ON premise_destination.premise_id = premise.id " + + "WHERE premise_destination.premise_id IN (" + premisePlaceholder + ") " + + "AND premise_destination.destination_node_id IN (" + nodePlaceholder + ") " + + "AND premise.user_id = ?"; // Create array with all parameters - Object[] params = new Object[premiseId.size() + 2]; - for (int i = 0; i < premiseId.size(); i++) { - params[i] = premiseId.get(i); - } - params[premiseId.size()] = nodeId; - params[premiseId.size() + 1] = userId; + Object[] params = new Object[allPremiseIds.size() + allNodeIds.size() + 1]; + int index = 0; - return jdbcTemplate.query(query, new DestinationMapper(), params); + for (Integer premiseId : allPremiseIds) { + params[index++] = premiseId; + } + for (Integer nodeId : allNodeIds) { + params[index++] = nodeId; + } + params[index] = userId; + + List allDestinations = jdbcTemplate.query(query, new DestinationMapper(), params); + + // Group destinations by premise ID and filter by the requested node IDs + Map> result = new HashMap<>(); + + for (Map.Entry> entry : premiseToNodes.entrySet()) { + Integer premiseId = entry.getKey(); + Set requestedNodeIds = new HashSet<>(entry.getValue()); + + List filteredDestinations = allDestinations.stream() + .filter(d -> d.getPremiseId().equals(premiseId) && + requestedNodeIds.contains(d.getDestinationNodeId())) + .collect(Collectors.toList()); + + result.put(premiseId, filteredDestinations); + } + + return result; } @Transactional diff --git a/src/main/java/de/avatic/lcc/service/access/DestinationService.java b/src/main/java/de/avatic/lcc/service/access/DestinationService.java index 384978f..283fd8d 100644 --- a/src/main/java/de/avatic/lcc/service/access/DestinationService.java +++ b/src/main/java/de/avatic/lcc/service/access/DestinationService.java @@ -3,14 +3,12 @@ package de.avatic.lcc.service.access; import de.avatic.lcc.dto.calculation.DestinationDTO; import de.avatic.lcc.dto.calculation.edit.destination.DestinationCreateDTO; import de.avatic.lcc.dto.calculation.edit.destination.DestinationSetDTO; -import de.avatic.lcc.dto.calculation.edit.destination.DestinationSetListItemDTO; import de.avatic.lcc.dto.calculation.edit.destination.DestinationUpdateDTO; import de.avatic.lcc.model.db.nodes.Node; import de.avatic.lcc.model.db.premises.Premise; import de.avatic.lcc.model.db.premises.route.*; import de.avatic.lcc.repositories.NodeRepository; import de.avatic.lcc.repositories.premise.*; -import de.avatic.lcc.repositories.properties.PropertyRepository; import de.avatic.lcc.repositories.users.UserNodeRepository; import de.avatic.lcc.service.calculation.RoutingService; import de.avatic.lcc.service.transformer.premise.DestinationTransformer; @@ -51,8 +49,52 @@ public class DestinationService { this.authorizationService = authorizationService; } + + private Map> processDestinations(List premisesToProcess, Map> destinationNodeIds, Integer annualAmount, Number repackingCost, Number disposalCost, Number handlingCost, Map> routes) { + + var destMap = new HashMap>(); + + for (var premise : premisesToProcess) { + + var destinations = new ArrayList(); + + for (var destinationNodeId : destinationNodeIds.get(premise.getId())) { + + Node destinationNode = nodeRepository.getById(destinationNodeId).orElseThrow(); + + var destination = new Destination(); + destination.setDestinationNodeId(destinationNodeId); + destination.setPremiseId(premise.getId()); + destination.setAnnualAmount(annualAmount); + destination.setD2d(false); + destination.setLeadTimeD2d(null); + destination.setRateD2d(null); + destination.setDisposalCost(disposalCost == null ? null : BigDecimal.valueOf(disposalCost.doubleValue())); + destination.setHandlingCost(handlingCost == null ? null : BigDecimal.valueOf(handlingCost.doubleValue())); + destination.setRepackingCost(repackingCost == null ? null : BigDecimal.valueOf(repackingCost.doubleValue())); + destination.setCountryId(destinationNode.getCountryId()); + destination.setGeoLat(destinationNode.getGeoLat()); + destination.setGeoLng(destinationNode.getGeoLng()); + destination.setId(destinationRepository.insert(destination)); + + Node source = premise.getSupplierNodeId() == null ? userNodeRepository.getById(premise.getUserSupplierNodeId()).orElseThrow() : nodeRepository.getById(premise.getSupplierNodeId()).orElseThrow(); + + if (routes != null) + //noinspection SpringTransactionalMethodCallsInspection + saveRoute(routes.get(new RouteIds(source.getId(), destinationNodeId, premise.getSupplierNodeId() == null)), destination.getId()); + + + destinations.add(destination); + } + + destMap.put(premise.getId(), destinations); + } + + return destMap; + } + @Transactional - public Map> setDestination(DestinationSetDTO dto) { + public Map> massSetDestinationProperties(DestinationSetDTO dto) { var admin = authorizationService.isSuper(); Integer userId = authorizationService.getUserId(); @@ -62,77 +104,69 @@ public class DestinationService { deleteAllDestinationsByPremiseId(dto.getPremiseId(), false); var premisses = premiseRepository.getPremisesById(dto.getPremiseId()); - Map> routes = findRoutes(premisses, dto.getDestinations().stream().map(DestinationSetListItemDTO::getDestinationNodeId).toList()); + // TODO no routing in set ... only props. +// Map> routes = findRoutes(premisses, dto.getDestinations().stream().map(DestinationSetListItemDTO::getDestinationNodeId).toList()); - Map> destinations = dto.getDestinations().stream() - .flatMap(destination -> createDestination(premisses, destination.getDestinationNodeId(), destination.getAnnualAmount(), destination.getRepackingCost(), destination.getDisposalCost(), destination.getHandlingCost(), routes).stream()) - .collect(Collectors.groupingBy(Destination::getPremiseId)); +// Map> destinations = dto.getDestinations().stream() +// .flatMap(destination -> processDestinations(premisses, destination.getDestinationNodeId(), destination.getAnnualAmount(), destination.getRepackingCost(), destination.getDisposalCost(), destination.getHandlingCost(), null).stream()) +// .collect(Collectors.groupingBy(Destination::getPremiseId)); - return destinations.entrySet().stream() - .collect(Collectors.toMap( - Map.Entry::getKey, - entry -> entry.getValue().stream().map(destinationTransformer::toDestinationDTO).toList())); +// return destinations.entrySet().stream() +// .collect(Collectors.toMap( +// Map.Entry::getKey, +// entry -> entry.getValue().stream().map(destinationTransformer::toDestinationDTO).toList())); + + return null; } + private List getDestinationToRemove(List oldDestinations, List newIds) { + return oldDestinations.stream().filter(dest -> !newIds.contains(dest.getDestinationNodeId())).map(Destination::getId).collect(Collectors.toList()); + } - private List createDestination(List premisesToProcess, Integer destinationNodeId, Integer annualAmount, Number repackingCost, Number disposalCost, Number handlingCost, Map> routes) { - - Node destinationNode = nodeRepository.getById(destinationNodeId).orElseThrow(); - - var destinations = new ArrayList(); - - for (var premise : premisesToProcess) { - var destination = new Destination(); - destination.setDestinationNodeId(destinationNodeId); - destination.setPremiseId(premise.getId()); - destination.setAnnualAmount(annualAmount); - destination.setD2d(false); - destination.setLeadTimeD2d(null); - destination.setRateD2d(null); - destination.setDisposalCost(disposalCost == null ? null : BigDecimal.valueOf(disposalCost.doubleValue())); - destination.setHandlingCost(handlingCost == null ? null : BigDecimal.valueOf(handlingCost.doubleValue())); - destination.setRepackingCost(repackingCost == null ? null : BigDecimal.valueOf(repackingCost.doubleValue())); - destination.setCountryId(destinationNode.getCountryId()); - destination.setGeoLat(destinationNode.getGeoLat()); - destination.setGeoLng(destinationNode.getGeoLng()); - destination.setId(destinationRepository.insert(destination)); - - Node source = premise.getSupplierNodeId() == null ? userNodeRepository.getById(premise.getUserSupplierNodeId()).orElseThrow() : nodeRepository.getById(premise.getSupplierNodeId()).orElseThrow(); - - //noinspection SpringTransactionalMethodCallsInspection - saveRoute(routes.get(new RouteIds(source.getId(), destinationNodeId, premise.getSupplierNodeId() == null)), destination.getId()); - - destinations.add(destination); - } - - return destinations; - + private List getNodeIdsToAdd(List oldDestinations, List newIds) { + var oldIds = oldDestinations.stream().map(Destination::getDestinationNodeId).toList(); + return newIds.stream().filter(id -> !oldIds.contains(id)).collect(Collectors.toList()); } @Transactional - public Map createDestination(DestinationCreateDTO dto) { + public Map> massSetDestinations(DestinationCreateDTO dto) { Integer userId = authorizationService.getUserId(); - var existingDestinations = destinationRepository.getByPremiseIdsAndNodeId(dto.getPremiseId(), dto.getDestinationNodeId(), userId); + Map> destinationsToProcess = new HashMap<>(); - var premisesIdsToProcess = new ArrayList(); - for (var premiseId : dto.getPremiseId()) { - if (existingDestinations.stream().map(Destination::getPremiseId).noneMatch(id -> id.equals(premiseId))) { - premisesIdsToProcess.add(premiseId); - } + var requestedDestMap = dto.getDestinationNodeIds(); + var existingDestMap = dto.getDestinationNodeIds().keySet().stream().collect(Collectors.toMap(id -> id, id -> destinationRepository.getByPremiseIdAndUserId(id, userId))); + + for (Integer premiseId : requestedDestMap.keySet()) { + var requestedDestinations = requestedDestMap.get(premiseId); + var existingDestinations = existingDestMap.getOrDefault(premiseId, Collections.emptyList()); + + /* remove deselected */ + var toRemove = getDestinationToRemove(existingDestinations, requestedDestinations); + deleteDestinationsById(toRemove, false); + + /* find new selected */ + var toAdd = getNodeIdsToAdd(existingDestinations, requestedDestinations); + destinationsToProcess.put(premiseId, toAdd); } - if (premisesIdsToProcess.isEmpty()) + if (destinationsToProcess.isEmpty()) return new HashMap<>(); - var premisses = premiseRepository.getPremisesById(premisesIdsToProcess); - Map> routes = findRoutes(premisses, Collections.singletonList(dto.getDestinationNodeId())); + var premisses = premiseRepository.getPremisesById(new ArrayList<>(destinationsToProcess.keySet())); + Map> routes = findRoutes(premisses, destinationsToProcess); - var destinations = createDestination(premisses, dto.getDestinationNodeId(), null, null, null, null, routes); - return destinations.stream().collect(Collectors.toMap(Destination::getPremiseId, destinationTransformer::toDestinationDTO)); + var destinations = processDestinations(premisses, destinationsToProcess, null, null, null, null, routes); + return destinations.entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + entry -> entry.getValue().stream() + .map(destinationTransformer::toDestinationDTO) + .toList() + )); } public DestinationDTO getDestination(Integer id) { @@ -170,14 +204,16 @@ public class DestinationService { } - private Map> findRoutes(List premisses, List destinationIds) { + private Map> findRoutes(List premisses, Map> routingRequest) { Map> routes = new HashMap<>(); Map nodes = new HashMap<>(); Map userNodes = new HashMap<>(); for (var premise : premisses) { - for (var destinationId : destinationIds) { + + for (var destinationId : routingRequest.get(premise.getId())) { + boolean isUserSupplierNode = (premise.getSupplierNodeId() == null); var ids = new RouteIds(isUserSupplierNode ? premise.getUserSupplierNodeId() : premise.getSupplierNodeId(), destinationId, isUserSupplierNode); if (routes.containsKey(ids)) continue; @@ -194,6 +230,8 @@ public class DestinationService { userNodes.put(premise.getUserSupplierNodeId(), userNodeRepository.getById(premise.getUserSupplierNodeId()).orElseThrow()); } + + //TODO in parallel routes.put(ids, routingService.findRoutes(nodes.get(destinationId), isUserSupplierNode ? userNodes.get(premise.getUserSupplierNodeId()) : nodes.get(premise.getSupplierNodeId()), isUserSupplierNode)); } } @@ -255,6 +293,11 @@ public class DestinationService { destinations.forEach(destination -> deleteDestinationById(destination.getId(), deleteRoutesOnly)); } + @Transactional + public void deleteDestinationsById(List ids, boolean deleteRoutesOnly) { + ids.forEach(id -> deleteDestinationById(id, deleteRoutesOnly)); + } + @Transactional public void deleteDestinationById(Integer id, boolean deleteRoutesOnly) { var admin = authorizationService.isSuper(); diff --git a/src/main/java/de/avatic/lcc/service/calculation/RoutingService.java b/src/main/java/de/avatic/lcc/service/calculation/RoutingService.java index e4767ae..26e4f9a 100644 --- a/src/main/java/de/avatic/lcc/service/calculation/RoutingService.java +++ b/src/main/java/de/avatic/lcc/service/calculation/RoutingService.java @@ -334,7 +334,7 @@ public class RoutingService { } finalSection.setRate(matrixRate); - finalSection.setApproxDistance(distanceService.getDistance(container.getSourceNode(), toNode, false)); + finalSection.setApproxDistance(distanceService.getDistance(container.getSourceNode(), toNode, true)); rates.add(finalSection); } @@ -699,7 +699,7 @@ public class RoutingService { if (matrixRate.isPresent()) { matrixRateObj.setRate(matrixRate.get()); - matrixRateObj.setApproxDistance(distanceService.getDistance(startNode, endNode, false)); + matrixRateObj.setApproxDistance(distanceService.getDistance(startNode, endNode, true)); container.getRates().add(matrixRateObj); return matrixRateObj; } else { diff --git a/src/test/java/de/avatic/lcc/controller/calculation/CalculationIntegrationTests.java b/src/test/java/de/avatic/lcc/controller/calculation/CalculationIntegrationTests.java index 2212ba4..921bee6 100644 --- a/src/test/java/de/avatic/lcc/controller/calculation/CalculationIntegrationTests.java +++ b/src/test/java/de/avatic/lcc/controller/calculation/CalculationIntegrationTests.java @@ -17,9 +17,7 @@ import org.springframework.test.context.jdbc.Sql; import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.annotation.Transactional; -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; +import java.util.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; @@ -62,8 +60,11 @@ public class CalculationIntegrationTests { var premise3 = premisesBeforeUpdate.stream().filter(p -> p.getHuUnitCount() == 3).findFirst().orElseThrow(); var createDto = new DestinationCreateDTO(); - createDto.setPremiseId(Collections.singletonList(premise1.getId())); - createDto.setDestinationNodeId(nodeId); + + var map = new HashMap>(); + map.put(premise1.getId(), List.of(nodeId)); + createDto.setDestinationNodeIds(map); + var response = mockMvc.perform(post("/api/calculation/destination") .content(objectMapper.writeValueAsString(createDto)) diff --git a/src/test/java/de/avatic/lcc/controller/calculation/DestinationIntegrationTest.java b/src/test/java/de/avatic/lcc/controller/calculation/DestinationIntegrationTest.java index 6c0cdb3..043b2b2 100644 --- a/src/test/java/de/avatic/lcc/controller/calculation/DestinationIntegrationTest.java +++ b/src/test/java/de/avatic/lcc/controller/calculation/DestinationIntegrationTest.java @@ -18,6 +18,7 @@ import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -61,8 +62,11 @@ public class DestinationIntegrationTest { var premise3 = premisesBeforeUpdate.stream().filter(p -> p.getHuUnitCount() == 3).findFirst().orElseThrow(); var dto = new DestinationCreateDTO(); - dto.setPremiseId(Arrays.asList(premise1.getId(), premise3.getId())); - dto.setDestinationNodeId(nodeId); + + var map = new HashMap>(); + map.put(premise1.getId(), List.of(nodeId)); + map.put(premise3.getId(), List.of(nodeId)); + dto.setDestinationNodeIds(map); mockMvc.perform(post("/api/calculation/destination") .content(objectMapper.writeValueAsString(dto))