Enhance deletion processes with event-check validations.
Added validation to prevent deletion of shipments, dewars, pucks, or samples if they have associated events. Updated frontend components to handle and display error messages based on API responses for improved user feedback.
This commit is contained in:
@ -30,6 +30,9 @@ from app.models import (
|
|||||||
Sample as SampleModel,
|
Sample as SampleModel,
|
||||||
DewarType as DewarTypeModel,
|
DewarType as DewarTypeModel,
|
||||||
DewarSerialNumber as DewarSerialNumberModel,
|
DewarSerialNumber as DewarSerialNumberModel,
|
||||||
|
LogisticsEvent,
|
||||||
|
PuckEvent,
|
||||||
|
SampleEvent,
|
||||||
)
|
)
|
||||||
from app.dependencies import get_db
|
from app.dependencies import get_db
|
||||||
import qrcode
|
import qrcode
|
||||||
@ -505,12 +508,55 @@ async def update_dewar(
|
|||||||
|
|
||||||
@router.delete("/{dewar_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{dewar_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_dewar(dewar_id: int, db: Session = Depends(get_db)):
|
async def delete_dewar(dewar_id: int, db: Session = Depends(get_db)):
|
||||||
|
# Fetch the Dewar from the database
|
||||||
dewar = db.query(DewarModel).filter(DewarModel.id == dewar_id).first()
|
dewar = db.query(DewarModel).filter(DewarModel.id == dewar_id).first()
|
||||||
|
|
||||||
if not dewar:
|
if not dewar:
|
||||||
raise HTTPException(status_code=404, detail="Dewar not found")
|
raise HTTPException(status_code=404, detail="Dewar not found")
|
||||||
|
|
||||||
db.delete(dewar)
|
# Check for associated logistics events
|
||||||
|
logistics_event_exists = (
|
||||||
|
db.query(LogisticsEvent).filter(LogisticsEvent.dewar_id == dewar_id).first()
|
||||||
|
)
|
||||||
|
if logistics_event_exists:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail=f"Dewar {dewar_id} has associated logistics events."
|
||||||
|
f"Deletion not allowed.",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check associated pucks and their events
|
||||||
|
for puck in dewar.pucks:
|
||||||
|
puck_event_exists = (
|
||||||
|
db.query(PuckEvent).filter(PuckEvent.puck_id == puck.id).first()
|
||||||
|
)
|
||||||
|
if puck_event_exists:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail=f"Puck {puck.id} "
|
||||||
|
f"associated with this Dewar has events."
|
||||||
|
f"Deletion not allowed.",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check associated samples and their events
|
||||||
|
for sample in puck.samples:
|
||||||
|
sample_event_exists = (
|
||||||
|
db.query(SampleEvent).filter(SampleEvent.sample_id == sample.id).first()
|
||||||
|
)
|
||||||
|
if sample_event_exists:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail=f"Sample {sample.id} "
|
||||||
|
f"associated with Puck {puck.id} has events."
|
||||||
|
f" Deletion not allowed.",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Perform cascade deletion: Delete samples, pucks, and the dewar
|
||||||
|
for puck in dewar.pucks:
|
||||||
|
for sample in puck.samples:
|
||||||
|
db.delete(sample) # Delete associated samples
|
||||||
|
db.delete(puck) # Delete associated puck
|
||||||
|
db.delete(dewar) # Finally, delete the dewar itself
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -15,6 +15,9 @@ from app.models import (
|
|||||||
Dewar as DewarModel,
|
Dewar as DewarModel,
|
||||||
Puck as PuckModel,
|
Puck as PuckModel,
|
||||||
Sample as SampleModel,
|
Sample as SampleModel,
|
||||||
|
LogisticsEvent,
|
||||||
|
SampleEvent,
|
||||||
|
PuckEvent,
|
||||||
)
|
)
|
||||||
from app.schemas import (
|
from app.schemas import (
|
||||||
ShipmentCreate,
|
ShipmentCreate,
|
||||||
@ -117,9 +120,60 @@ async def create_shipment(shipment: ShipmentCreate, db: Session = Depends(get_db
|
|||||||
|
|
||||||
@router.delete("/{shipment_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{shipment_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_shipment(shipment_id: int, db: Session = Depends(get_db)):
|
async def delete_shipment(shipment_id: int, db: Session = Depends(get_db)):
|
||||||
|
# Fetch the shipment
|
||||||
shipment = db.query(ShipmentModel).filter(ShipmentModel.id == shipment_id).first()
|
shipment = db.query(ShipmentModel).filter(ShipmentModel.id == shipment_id).first()
|
||||||
if not shipment:
|
if not shipment:
|
||||||
raise HTTPException(status_code=404, detail="Shipment not found")
|
raise HTTPException(status_code=404, detail="Shipment not found")
|
||||||
|
|
||||||
|
# Check associated dewars
|
||||||
|
dewars = db.query(DewarModel).filter(DewarModel.shipment_id == shipment_id).all()
|
||||||
|
if dewars:
|
||||||
|
# Ensure dewars, pucks, and samples have no associated events
|
||||||
|
for dewar in dewars:
|
||||||
|
if (
|
||||||
|
db.query(LogisticsEvent)
|
||||||
|
.filter(LogisticsEvent.dewar_id == dewar.id)
|
||||||
|
.first()
|
||||||
|
):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail=f"Dewar {dewar.id} has associated logistics events."
|
||||||
|
f"Shipment cannot be deleted.",
|
||||||
|
)
|
||||||
|
|
||||||
|
for puck in dewar.pucks:
|
||||||
|
if db.query(PuckEvent).filter(PuckEvent.puck_id == puck.id).first():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail=f"Puck {puck.id}"
|
||||||
|
f" in Dewar {dewar.id}"
|
||||||
|
f" has associated puck events."
|
||||||
|
f"Shipment cannot be deleted.",
|
||||||
|
)
|
||||||
|
|
||||||
|
for sample in puck.samples:
|
||||||
|
if (
|
||||||
|
db.query(SampleEvent)
|
||||||
|
.filter(SampleEvent.sample_id == sample.id)
|
||||||
|
.first()
|
||||||
|
):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail=f"Sample {sample.id}"
|
||||||
|
f" in Puck {puck.id}"
|
||||||
|
f" has associated sample events."
|
||||||
|
f"Shipment cannot be deleted.",
|
||||||
|
)
|
||||||
|
|
||||||
|
# If no events are found, proceed to delete the shipment
|
||||||
|
for dewar in dewars:
|
||||||
|
for puck in dewar.pucks:
|
||||||
|
for sample in puck.samples:
|
||||||
|
db.delete(sample) # Delete associated samples
|
||||||
|
db.delete(puck) # Delete associated pucks
|
||||||
|
db.delete(dewar) # Delete the dewar itself
|
||||||
|
|
||||||
|
# Finally, delete the shipment
|
||||||
db.delete(shipment)
|
db.delete(shipment)
|
||||||
db.commit()
|
db.commit()
|
||||||
return
|
return
|
||||||
@ -228,19 +282,63 @@ async def add_dewar_to_shipment(
|
|||||||
async def remove_dewar_from_shipment(
|
async def remove_dewar_from_shipment(
|
||||||
shipment_id: int, dewar_id: int, db: Session = Depends(get_db)
|
shipment_id: int, dewar_id: int, db: Session = Depends(get_db)
|
||||||
):
|
):
|
||||||
|
# Fetch the shipment
|
||||||
shipment = db.query(ShipmentModel).filter(ShipmentModel.id == shipment_id).first()
|
shipment = db.query(ShipmentModel).filter(ShipmentModel.id == shipment_id).first()
|
||||||
if not shipment:
|
if not shipment:
|
||||||
raise HTTPException(status_code=404, detail="Shipment not found")
|
raise HTTPException(status_code=404, detail="Shipment not found")
|
||||||
|
|
||||||
dewar_exists = any(dw.id == dewar_id for dw in shipment.dewars)
|
# Check if the dewar belongs to the shipment
|
||||||
if not dewar_exists:
|
dewar = (
|
||||||
|
db.query(DewarModel)
|
||||||
|
.filter(DewarModel.id == dewar_id, DewarModel.shipment_id == shipment_id)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
if not dewar:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=404, detail=f"Dewar with ID {dewar_id} not found in shipment"
|
status_code=404,
|
||||||
|
detail=f"Dewar with ID {dewar_id} not found in shipment {shipment_id}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Check for logistics events associated with the dewar
|
||||||
|
logistics_event_exists = (
|
||||||
|
db.query(LogisticsEvent).filter(LogisticsEvent.dewar_id == dewar_id).first()
|
||||||
|
)
|
||||||
|
if logistics_event_exists:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail=f"Dewar {dewar_id} has " f" logistics events. Removal not allowed.",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check associated pucks and their events
|
||||||
|
for puck in dewar.pucks:
|
||||||
|
puck_event_exists = (
|
||||||
|
db.query(PuckEvent).filter(PuckEvent.puck_id == puck.id).first()
|
||||||
|
)
|
||||||
|
if puck_event_exists:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail=f"Puck {puck.id} in Dewar {dewar_id}"
|
||||||
|
f" has associated events. Removal not allowed.",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check associated samples and their events
|
||||||
|
for sample in puck.samples:
|
||||||
|
sample_event_exists = (
|
||||||
|
db.query(SampleEvent).filter(SampleEvent.sample_id == sample.id).first()
|
||||||
|
)
|
||||||
|
if sample_event_exists:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail=f"Sample {sample.id} "
|
||||||
|
f"in Puck {puck.id} "
|
||||||
|
f"has associated events. Removal not allowed.",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Unlink the dewar from the shipment
|
||||||
shipment.dewars = [dw for dw in shipment.dewars if dw.id != dewar_id]
|
shipment.dewars = [dw for dw in shipment.dewars if dw.id != dewar_id]
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(shipment)
|
db.refresh(shipment)
|
||||||
|
|
||||||
return shipment
|
return shipment
|
||||||
|
|
||||||
|
|
||||||
|
@ -78,13 +78,27 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
|||||||
const confirmed = window.confirm('Are you sure you want to delete this dewar?');
|
const confirmed = window.confirm('Are you sure you want to delete this dewar?');
|
||||||
if (confirmed && selectedShipment) {
|
if (confirmed && selectedShipment) {
|
||||||
try {
|
try {
|
||||||
const updatedShipment = await ShipmentsService.removeDewarFromShipmentShipmentsShipmentIdRemoveDewarDewarIdDelete(selectedShipment.id, dewarId);
|
const updatedShipment = await ShipmentsService.removeDewarFromShipmentShipmentsShipmentIdRemoveDewarDewarIdDelete(
|
||||||
|
selectedShipment.id,
|
||||||
|
dewarId
|
||||||
|
);
|
||||||
setSelectedShipment(updatedShipment);
|
setSelectedShipment(updatedShipment);
|
||||||
setLocalSelectedDewar(null);
|
setLocalSelectedDewar(null);
|
||||||
refreshShipments();
|
refreshShipments();
|
||||||
} catch (error) {
|
alert('Dewar deleted successfully!');
|
||||||
console.error('Failed to delete dewar:', error);
|
} catch (error: any) {
|
||||||
alert('Failed to delete dewar. Please try again.');
|
console.error('Full error object:', error);
|
||||||
|
|
||||||
|
let errorMessage = 'Failed to delete dewar. Please try again.';
|
||||||
|
|
||||||
|
if (error instanceof ApiError && error.body) {
|
||||||
|
console.error('API error body:', error.body);
|
||||||
|
errorMessage = error.body.detail || errorMessage;
|
||||||
|
} else if (error.message) {
|
||||||
|
errorMessage = error.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
alert(`Failed to delete dewar: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
|||||||
import { Button, Box, Typography, IconButton } from '@mui/material';
|
import { Button, Box, Typography, IconButton } from '@mui/material';
|
||||||
import { Add as AddIcon, Delete as DeleteIcon, UploadFile as UploadFileIcon, Refresh as RefreshIcon } from '@mui/icons-material';
|
import { Add as AddIcon, Delete as DeleteIcon, UploadFile as UploadFileIcon, Refresh as RefreshIcon } from '@mui/icons-material';
|
||||||
import UploadDialog from './UploadDialog';
|
import UploadDialog from './UploadDialog';
|
||||||
import { Dewar, Shipment, ShipmentsService } from '../../openapi';
|
import {ApiError, Dewar, Shipment, ShipmentsService} from '../../openapi';
|
||||||
import { SxProps } from '@mui/material';
|
import { SxProps } from '@mui/material';
|
||||||
import bottleGrey from '/src/assets/icons/bottle-svgrepo-com-grey.svg';
|
import bottleGrey from '/src/assets/icons/bottle-svgrepo-com-grey.svg';
|
||||||
import bottleYellow from '/src/assets/icons/bottle-svgrepo-com-yellow.svg';
|
import bottleYellow from '/src/assets/icons/bottle-svgrepo-com-yellow.svg';
|
||||||
@ -39,21 +39,34 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
|
|||||||
|
|
||||||
const handleDeleteShipment = async () => {
|
const handleDeleteShipment = async () => {
|
||||||
if (selectedShipment) {
|
if (selectedShipment) {
|
||||||
const confirmed = window.confirm(`Are you sure you want to delete the shipment: ${selectedShipment.shipment_name}?`);
|
const confirmDelete = window.confirm(
|
||||||
if (confirmed) {
|
`Are you sure you want to delete the shipment: ${selectedShipment.shipment_name}?`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!confirmDelete) return;
|
||||||
|
|
||||||
|
// Try to delete the shipment
|
||||||
await deleteShipment(selectedShipment.id);
|
await deleteShipment(selectedShipment.id);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteShipment = async (shipmentId: number) => {
|
const deleteShipment = async (shipmentId: number) => {
|
||||||
if (!shipmentId) return;
|
if (!shipmentId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await ShipmentsService.deleteShipmentShipmentsShipmentIdDelete(shipmentId);
|
await ShipmentsService.deleteShipmentShipmentsShipmentIdDelete(shipmentId);
|
||||||
refreshShipments();
|
refreshShipments();
|
||||||
selectShipment(null);
|
selectShipment(null);
|
||||||
} catch (error) {
|
alert("Shipment deleted successfully.");
|
||||||
console.error('Failed to delete shipment:', error);
|
} catch (error: any) {
|
||||||
|
console.error("Failed to delete shipment:", error);
|
||||||
|
|
||||||
|
let errorMessage = "Failed to delete shipment.";
|
||||||
|
if (error instanceof ApiError && error.body) {
|
||||||
|
errorMessage = error.body.detail || errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
alert(errorMessage);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user