Add bulk update functionality for destination data

- Introduced `DestinationMassUpdateDTO` to handle bulk update payload.
- Added new API endpoint `/destination/all` for mass updates in `PremiseController`.
- Updated frontend logic to support mass updating of destinations and enhanced matrix calculations with debounce.
- Improved handling of destination and route processing across components.
This commit is contained in:
Jan 2025-12-05 17:36:01 +01:00
parent a40a8c6bb4
commit cbd467d3b0
8 changed files with 168 additions and 35 deletions

View file

@ -107,7 +107,7 @@ export default {
computed: {
...mapStores(useDestinationEditStore, usePremiseEditStore),
rows() {
return this.destinationEditStore.getHandlingCostMatrix
return this.destinationEditStore.getHandlingCostMatrix ?? [];
},
allChecked() {
return this.rows.every(r => r.selected);
@ -128,7 +128,10 @@ export default {
async created() {
this.onLoadingChange(true);
try {
await this.buildMatrix();
await new Promise(resolve => setTimeout(() => {
this.buildMatrix();
resolve();
}, 10));
} finally {
this.onLoadingChange(false);

View file

@ -77,7 +77,7 @@ export default {
computed: {
...mapStores(useDestinationEditStore, usePremiseEditStore),
rows() {
return this.destinationEditStore.getQuantityMatrix;
return this.destinationEditStore.getQuantityMatrix ?? [];
},
allChecked() {
return this.rows.every(r => r.selected);
@ -98,7 +98,10 @@ export default {
async created() {
this.onLoadingChange(true);
try {
await this.buildMatrix();
await new Promise(resolve => setTimeout(() => {
this.buildMatrix();
resolve();
}, 10));
} finally {
this.onLoadingChange(false);
}

View file

@ -62,7 +62,7 @@ export default {
computed: {
...mapStores(useDestinationEditStore, usePremiseEditStore),
rows() {
return this.destinationEditStore.getRouteMatrix;
return this.destinationEditStore.getRouteMatrix ?? [];
}
},
data() {
@ -75,7 +75,10 @@ export default {
async created() {
this.onLoadingChange(true);
try {
await this.buildMatrix();
await new Promise(resolve => setTimeout(() => {
this.buildMatrix();
resolve();
}, 10));
} finally {
this.onLoadingChange(false);
}
@ -116,13 +119,16 @@ export default {
const destOfCurPremise = this.destinationEditStore.getByPremiseId(pId);
if (!destOfCurPremise) continue;
/* supplier map collects all destinations for one supplier
* and replaces the destination if the same destination is found
* that already has a selected route.
/* supplier map collects all destinations for one supplier.
* if there is more than one instance of a destination for one supplier
* (more than one part number), a destination instance is chosen by the following priority list:
* 1. instances with d2d rate
* 2. instances with selected routes
* 3. all other instances.
*/
if (!supplierToDestinationsMap.has(curPremise.supplier.id)) {
supplierToDestinationsMap.set(curPremise.supplier.id, {
destinations: destOfCurPremise ?? []
destinations: [...destOfCurPremise]
});
} else {
const mapEntry = supplierToDestinationsMap.get(curPremise.supplier.id);
@ -144,8 +150,8 @@ export default {
})
}
/* destination map collects all destinations over all
* suppliers for table headers
/* Collects all destinations over all
* suppliers and part numbers for the table headers
*/
for (const d of destOfCurPremise) {
const destId = d.destination_node.id;
@ -166,23 +172,23 @@ export default {
const premiseMap = new Map();
this.premiseIds.forEach(pId => {
const premise = this.premiseEditStore.getById(pId);
const destinations = this.destinationEditStore.getByPremiseId(pId);
const curPremise = this.premiseEditStore.getById(pId);
const destOfCurPremise = this.destinationEditStore.getByPremiseId(pId);
if (!premiseMap.has(premise.supplier.id)) {
premiseMap.set(premise.supplier.id, {
if (!premiseMap.has(curPremise.supplier.id)) {
premiseMap.set(curPremise.supplier.id, {
ids: [],
supplierNodeId: premise.supplier.id,
supplier: premise.supplier,
destinations: this.buildDestinations(columnHeadersMap, supplierToDestinationsMap.get(premise.supplier.id)?.destinations ?? [])
supplierNodeId: curPremise.supplier.id,
supplier: curPremise.supplier,
destinations: this.buildDestinations(columnHeadersMap, supplierToDestinationsMap.get(curPremise.supplier.id)?.destinations ?? [])
});
}
const row = premiseMap.get(premise.supplier.id);
const row = premiseMap.get(curPremise.supplier.id);
if (row) {
row.ids.push(premise.id);
this.addDestinationsToRow(row.destinations, destinations)
row.ids.push(curPremise.id);
this.addDestinationsToRow(row.destinations, destOfCurPremise)
}
});
@ -215,17 +221,25 @@ export default {
}
});
},
addDestinationsToRow(rowDestinations, premiseDestinations) {
premiseDestinations.forEach(premD => {
addDestinationsToRow(rowDestinations, destOfPremises) {
destOfPremises.forEach(curDestOfPremise => {
let existingDest = rowDestinations.find(rowD => rowD.destinationNodeId === premD.destination_node.id) ?? null;
/* rowDestinations contains all here known destinations that are shown
*
*
*/
let existingDest = rowDestinations.find(rowD => rowD.destinationNodeId === curDestOfPremise.destination_node.id) ?? null;
if (existingDest) {
existingDest.disabled = false;
existingDest.ids.push(premD.id);
if(existingDest.ids.includes(curDestOfPremise.id))
console.log("Duplicate id: ", curDestOfPremise.id);
existingDest.ids.push(curDestOfPremise.id);
/* add route ids to routes */
this.verifyRoutes(existingDest, premD)
this.verifyRoutes(existingDest, curDestOfPremise)
}
});
},
@ -242,9 +256,13 @@ export default {
premiseRoutes.forEach(route => {
const routeString = JSON.stringify(route.transit_nodes.map(n => n.external_mapping_id)); //.join(" > ").replace("_", " ");
if (!(rowDest.routes.some(r => r.routeCompareString === routeString && r.type === route.type))) {
const rowRoute = rowDest.routes.find(r => r.routeCompareString === routeString && r.type === route.type);
if (!rowRoute) {
console.log("no matching route ", routeString, rowDest);
rowDest.valid = false;
} else {
rowRoute.ids.push(route.id);
}
});
},
@ -252,6 +270,7 @@ export default {
return routes?.map(r => {
return {
ids: [],
type: r.type,
selected: r.is_selected,
transitNodes: r.transit_nodes.map(n => n.external_mapping_id),

View file

@ -439,6 +439,8 @@ export default {
},
async closeEditModalAction(action) {
let massUpdate = false;
if (this.modalType === 'amount' || this.modalType === 'routes' || this.modalType === "destinations") {
if (action === 'accept') {
@ -450,7 +452,7 @@ export default {
await this.destinationEditStore.massSetDestinations(setMatrix);
}
} else {
await this.destinationEditStore.massUpdateDestinations();
massUpdate = true
}
}
@ -458,6 +460,10 @@ export default {
this.fillData(this.modalType);
this.modalType = null;
if(massUpdate) {
await this.destinationEditStore.massUpdateDestinations(this.editIds);
}
if (this.modalStash && action === 'accept') {
setTimeout(() => {
this.openModal(this.modalStash.type, this.modalStash.ids, this.modalStash.dataSource, this.modalStash.massEdit);

View file

@ -108,15 +108,87 @@ export const useDestinationEditStore = defineStore('destinationEdit', {
this.loading = false;
},
async massUpdateDestinations() {
async massUpdateDestinations(premiseIds) {
this.loading = true;
this.updateQuantity();
this.updateHandlingCosts();
await new Promise(resolve => setTimeout(() => {
this.updateQuantity();
resolve();
}, 10));
await new Promise(resolve => setTimeout(() => {
this.updateHandlingCosts();
resolve();
}, 10));
await new Promise(resolve => setTimeout(() => {
this.updateRoutes();
resolve();
}, 10));
const destinationMap = new Map();
console.log("premiseids", premiseIds )
premiseIds.forEach(premiseId => {
this.destinations.get(premiseId)?.forEach(toUpdate => {
console.log("toUpdate", toUpdate)
destinationMap.set(toUpdate.id,{
annual_amount: toUpdate.annual_amount,
repackaging_costs: toUpdate.repackaging_costs,
handling_costs: toUpdate.handling_costs,
disposal_costs: toUpdate.disposal_costs,
is_d2d: toUpdate.is_d2d,
rate_d2d: toUpdate.rate_d2d,
lead_time_d2d: toUpdate.lead_time_d2d,
route_selected_id: toUpdate.routes.find(r => r.is_selected)?.id ?? null,
})
} )
});
console.log("destmap",destinationMap)
await performRequest(this, 'PUT', `${config.backendUrl}/calculation/destination/all`, {destinations: Object.fromEntries(destinationMap)}, false);
this.loading = false;
},
updateRoutes() {
this.routeMatrix.forEach(row => {
row.ids.forEach(premiseId => {
row.destinations.forEach(destinationUpdateInfo => {
const destOfCurPremisses = this.destinations.get(premiseId);
if ((destOfCurPremisses ?? null) !== null) {
const destOfCurPremise = destOfCurPremisses.find(d => destinationUpdateInfo.destinationNodeId === d.destination_node.id);
if(destinationUpdateInfo.ids.includes(destOfCurPremise.id)) {
/* set d2d stuff */
destOfCurPremise.is_d2d = destinationUpdateInfo.isD2d;
destOfCurPremise.rate_d2d = destinationUpdateInfo.rateD2d;
destOfCurPremise.lead_time_d2d = destinationUpdateInfo.leadTimeD2d;
/* set selected route */
const selectedRoute = destinationUpdateInfo.routes?.find(r => r.routeCompareString === destinationUpdateInfo.selectedRoute);
destOfCurPremise.routes.forEach(r => r.is_selected = false);
if(selectedRoute) {
const routeOfCurPremise = destOfCurPremise.routes.find(r => selectedRoute.ids.includes(r.id));
if(routeOfCurPremise) {
routeOfCurPremise.is_selected = true;
}
}
}
}
});
});
});
},
updateHandlingCosts() {
this.handlingCostMatrix.forEach(row => {
const destinations = this.destinations.get(row.id);
@ -125,7 +197,7 @@ export const useDestinationEditStore = defineStore('destinationEdit', {
const destination = destinations.find(dest => dest.id === row.destinationId);
if((destination ?? null) !== null) {
if ((destination ?? null) !== null) {
destination.disposal_costs = row.disposal_costs;
destination.repackaging_costs = row.repackaging_costs;
destination.handling_costs = row.handling_costs;

View file

@ -9,6 +9,7 @@ import de.avatic.lcc.dto.calculation.create.CreatePremiseDTO;
import de.avatic.lcc.dto.calculation.create.PremiseSearchResultDTO;
import de.avatic.lcc.dto.calculation.edit.PremiseDetailDTO;
import de.avatic.lcc.dto.calculation.edit.destination.DestinationCreateDTO;
import de.avatic.lcc.dto.calculation.edit.destination.DestinationMassUpdateDTO;
import de.avatic.lcc.dto.calculation.edit.destination.DestinationSetDTO;
import de.avatic.lcc.dto.calculation.edit.destination.DestinationUpdateDTO;
import de.avatic.lcc.dto.calculation.edit.masterData.MaterialUpdateDTO;
@ -128,7 +129,8 @@ public class PremiseController {
@GetMapping({"/edit", "/edit/"})
@PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')")
public ResponseEntity<List<PremiseDetailDTO>> getPremises(@RequestParam List<Integer> premissIds) {
return ResponseEntity.ok(premisesServices.getPremises(premissIds));
var premisses = premisesServices.getPremises(premissIds);
return ResponseEntity.ok(premisses);
}
@PutMapping({"/start", "/start/"})
@ -198,6 +200,13 @@ public class PremiseController {
return ResponseEntity.ok().build();
}
@PutMapping({"/destination/all", "/destination/all/"})
@PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')")
public ResponseEntity<Void> updateAllDestination(@RequestBody @Valid DestinationMassUpdateDTO destinationUpdateDTO) {
destinationUpdateDTO.getDestinations().forEach(destinationService::updateDestination);
return ResponseEntity.ok().build();
}
@DeleteMapping({"/destination/{id}", "/destination/{id}/"})
@PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')")
public ResponseEntity<Void> deleteDestination(@PathVariable Integer id) {

View file

@ -0,0 +1,19 @@
package de.avatic.lcc.dto.calculation.edit.destination;
import jakarta.validation.Valid;
import java.util.Map;
public class DestinationMassUpdateDTO {
Map<Integer, @Valid DestinationUpdateDTO> destinations;
public Map<Integer, DestinationUpdateDTO> getDestinations() {
return destinations;
}
public void setDestinations(Map<Integer, DestinationUpdateDTO> destinations) {
this.destinations = destinations;
}
}

View file

@ -96,7 +96,9 @@ public class PremisesService {
if (!admin)
premiseRepository.checkOwner(premiseIds, userId);
return premiseRepository.getPremisesById(premiseIds).stream().filter(p -> p.getState().equals(PremiseState.DRAFT)).map(premiseTransformer::toPremiseDetailDTO).toList();
var premisses = premiseRepository.getPremisesById(premiseIds).stream().filter(p -> p.getState().equals(PremiseState.DRAFT)).toList();
return premisses.stream().map(premiseTransformer::toPremiseDetailDTO).toList();
}