now creating dewars, pucks and samples from spreadsheet and replacing dewars if a dewar with the same name exists

This commit is contained in:
GotthardG 2024-11-12 17:03:38 +01:00
parent 86883133a7
commit 8f7c90bab0
5 changed files with 327 additions and 134 deletions

View File

@ -1,4 +1,4 @@
from sqlalchemy import Column, Integer, String, Date, ForeignKey
from sqlalchemy import Column, Integer, String, Date, ForeignKey, JSON
from sqlalchemy.orm import relationship
from app.database import Base
from app.calculations import calculate_number_of_pucks, calculate_number_of_samples
@ -107,6 +107,7 @@ class Sample(Base):
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
sample_name = Column(String, index=True) # Matches `sample_name` in data creation
position = Column(Integer) # Matches `position` in data creation script
data_collection_parameters = Column(JSON, nullable=True)
# Foreign keys and relationships
puck_id = Column(Integer, ForeignKey('pucks.id'))

View File

@ -1,9 +1,12 @@
from fastapi import APIRouter, HTTPException, status, Depends
from sqlalchemy.orm import Session, joinedload
from typing import List
import uuid
import logging
from sqlalchemy.exc import SQLAlchemyError
from pydantic import ValidationError
from app.schemas import Dewar as DewarSchema, DewarCreate, DewarUpdate
from app.models import Dewar as DewarModel, Puck as PuckModel
from app.models import Dewar as DewarModel, Puck as PuckModel, \
Sample as SampleModel # Assuming SampleModel is defined in models
from app.dependencies import get_db
router = APIRouter()
@ -17,24 +20,55 @@ async def get_dewars(db: Session = Depends(get_db)):
@router.post("/", response_model=DewarSchema, status_code=status.HTTP_201_CREATED)
async def create_dewar(dewar: DewarCreate, db: Session = Depends(get_db)) -> DewarSchema:
db_dewar = DewarModel(
dewar_name=dewar.dewar_name,
tracking_number=dewar.tracking_number,
status=dewar.status,
ready_date=dewar.ready_date,
shipping_date=dewar.shipping_date,
arrival_date=dewar.arrival_date,
returning_date=dewar.returning_date,
qrcode=dewar.qrcode,
contact_person_id=dewar.contact_person_id,
return_address_id=dewar.return_address_id
)
try:
db_dewar = DewarModel(
dewar_name=dewar.dewar_name,
tracking_number=dewar.tracking_number,
status=dewar.status,
ready_date=dewar.ready_date,
shipping_date=dewar.shipping_date,
arrival_date=dewar.arrival_date,
returning_date=dewar.returning_date,
qrcode=dewar.qrcode,
contact_person_id=dewar.contact_person_id,
return_address_id=dewar.return_address_id
)
db.add(db_dewar)
db.commit()
db.refresh(db_dewar)
db.add(db_dewar)
db.commit()
db.refresh(db_dewar)
return db_dewar
for puck_data in dewar.pucks:
puck = PuckModel(
dewar_id=db_dewar.id,
puck_name=puck_data.puck_name,
puck_type=puck_data.puck_type,
puck_location_in_dewar=puck_data.puck_location_in_dewar,
)
db.add(puck)
db.commit()
db.refresh(puck)
for sample_data in puck_data.samples:
sample = SampleModel(
puck_id=puck.id,
sample_name=sample_data.sample_name,
position=sample_data.position,
# Ensure only valid attributes are set
data_collection_parameters=sample_data.data_collection_parameters,
)
db.add(sample)
db.commit()
db.refresh(sample)
return db_dewar
except SQLAlchemyError as e:
logging.error(f"Database error occurred: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
except ValidationError as e:
logging.error(f"Validation error occurred: {e}")
raise HTTPException(status_code=400, detail="Validation error")
@router.get("/{dewar_id}", response_model=DewarSchema)

View File

@ -9,7 +9,7 @@ from sqlalchemy.exc import SQLAlchemyError
from app.models import Shipment as ShipmentModel, ContactPerson as ContactPersonModel, Address as AddressModel, \
Proposal as ProposalModel, Dewar as DewarModel, Puck as PuckModel, Sample as SampleModel
from app.schemas import ShipmentCreate, UpdateShipmentComments, Shipment as ShipmentSchema, DewarUpdate, \
ContactPerson as ContactPersonSchema, Sample as SampleSchema, DewarCreate, PuckCreate, SampleCreate
ContactPerson as ContactPersonSchema, Sample as SampleSchema, DewarCreate, PuckCreate, SampleCreate, DewarSchema
from app.database import get_db
from app.crud import get_shipments, get_shipment_by_id
@ -38,6 +38,19 @@ async def fetch_shipments(id: Optional[int] = Query(None), db: Session = Depends
logging.info(f"Shipment ID: {shipment.id}, Shipment Name: {shipment.shipment_name}")
return shipments
@router.get("/{shipment_id}/dewars", response_model=List[DewarSchema])
async def get_dewars_by_shipment_id(shipment_id: int, db: Session = Depends(get_db)):
shipment = db.query(ShipmentModel).filter(ShipmentModel.id == shipment_id).first()
if not shipment:
raise HTTPException(status_code=404, detail="Shipment not found")
dewars = db.query(DewarModel).filter(DewarModel.shipment_id == shipment_id).all()
if not dewars:
raise HTTPException(status_code=404, detail="No dewars found for this shipment")
return dewars
@router.post("", response_model=ShipmentSchema, status_code=status.HTTP_201_CREATED)
async def create_shipment(shipment: ShipmentCreate, db: Session = Depends(get_db)):
@ -220,58 +233,55 @@ async def update_shipment_comments(shipment_id: int, comments_data: UpdateShipme
@router.post("/{shipment_id}/add_dewar_puck_sample", response_model=ShipmentSchema, status_code=status.HTTP_201_CREATED)
async def add_dewar_puck_sample_to_shipment(
shipment_id: int,
payload: DewarCreate,
db: Session = Depends(get_db)
):
def add_dewar_puck_sample_to_shipment(shipment_id: int, payload: DewarCreate, db: Session = Depends(get_db)):
shipment = db.query(ShipmentModel).filter(ShipmentModel.id == shipment_id).first()
if not shipment:
logging.error(f"Shipment not found with ID: {shipment_id}")
raise HTTPException(status_code=404, detail="Shipment not found")
try:
dewar = DewarModel(
shipment_id=shipment_id,
dewar_name=payload.dewar_name,
tracking_number=payload.tracking_number,
status=payload.status,
contact_person_id=payload.contact_person_id,
return_address_id=payload.return_address_id,
)
db.add(dewar)
db.commit()
db.refresh(dewar)
for puck_data in payload.pucks:
puck = PuckModel(
dewar_id=dewar.id,
puck_name=puck_data.puck_name,
puck_type=puck_data.puck_type,
puck_location_in_dewar=puck_data.puck_location_in_dewar,
)
db.add(puck)
db.commit()
db.refresh(puck)
for sample_data in puck_data.samples:
sample = SampleModel(
puck_id=puck.id,
sample_name=sample_data.sample_name,
position=sample_data.position,
data_collection_parameters=sample_data.data_collection_parameters,
)
db.add(sample)
for dewar_data in payload.dewars:
dewar = db.query(DewarModel).filter(DewarModel.dewar_name == dewar_data.dewar_name).first()
if dewar:
# Update existing dewar
dewar.tracking_number = dewar_data.tracking_number
dewar.status = dewar_data.status
db.commit()
db.refresh(sample)
else:
dewar = DewarModel(
shipment_id=shipment_id,
dewar_name=dewar_data.dewar_name,
tracking_number=dewar_data.tracking_number,
status=dewar_data.status,
)
db.add(dewar)
db.commit()
db.refresh(dewar)
for puck_data in dewar_data.pucks:
puck = PuckModel(
dewar_id=dewar.id,
puck_name=puck_data.puck_name,
puck_type=puck_data.puck_type,
puck_location_in_dewar=puck_data.puck_location_in_dewar,
)
db.add(puck)
db.commit()
db.refresh(puck)
for sample_data in puck_data.samples:
sample = SampleModel(
puck_id=puck.id,
sample_name=sample_data.sample_name,
position=sample_data.position,
)
db.add(sample)
db.commit()
db.refresh(sample)
db.refresh(shipment)
except SQLAlchemyError as e:
logging.error(f"Database error occurred: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
raise HTTPException(status_code=500, detail=f"Database error: {e}")
except ValidationError as e:
logging.error(f"Validation error occurred: {e}")
raise HTTPException(status_code=400, detail="Validation error")
raise HTTPException(status_code=400, detail=f"Validation error: {e}")
logging.info(f"Successfully added dewar, puck, and sample for shipment ID: {shipment_id}")
return shipment
return shipment

View File

@ -101,7 +101,8 @@ class Sample(BaseModel):
class SampleCreate(BaseModel):
sample_name: str = Field(..., alias="crystalname")
position: int = Field(..., alias="positioninpuck")
data_collection_parameters: DataCollectionParameters
data_collection_parameters: Optional[DataCollectionParameters] = None
results: Optional[Results] = None
class Config:
populate_by_name = True
@ -115,8 +116,11 @@ class PuckBase(BaseModel):
puck_location_in_dewar: int
class PuckCreate(PuckBase):
pass
class PuckCreate(BaseModel):
puck_name: str
puck_type: str
puck_location_in_dewar: int
samples: List[SampleCreate] = []
class PuckUpdate(BaseModel):
@ -152,6 +156,7 @@ class DewarBase(BaseModel):
qrcode: str
contact_person_id: Optional[int]
return_address_id: Optional[int]
pucks: List[PuckCreate] = []
class DewarCreate(DewarBase):
@ -181,6 +186,17 @@ class DewarUpdate(BaseModel):
contact_person_id: Optional[int] = None
address_id: Optional[int] = None
class DewarSchema(BaseModel):
id: int
dewar_name: str
tracking_number: str
status: str
contact_person_id: int
return_address_id: int
class Config:
from_attributes = True
# Proposal schemas
class Proposal(BaseModel):

View File

@ -11,7 +11,12 @@ import {
TextField,
Typography,
Button,
Box
Box,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle
} from '@mui/material';
import { SpreadsheetService, ShipmentsService, DewarsService, ApiError } from '../../openapi';
import * as ExcelJS from 'exceljs';
@ -30,6 +35,9 @@ const SpreadsheetTable = ({
const [editingCell, setEditingCell] = useState({});
const [nonEditableCells, setNonEditableCells] = useState(new Set());
const [isSubmitting, setIsSubmitting] = useState(false);
const [showUpdateDialog, setShowUpdateDialog] = useState(false);
const [dewarsToReplace, setDewarsToReplace] = useState([]);
const [dewarsToCreate, setDewarsToCreate] = useState(new Map());
const initialNewDewarState = {
number_of_pucks: 0,
@ -44,7 +52,7 @@ const SpreadsheetTable = ({
dewar_name: '',
tracking_number: 'UNKNOWN',
status: 'In preparation',
pucks: []
pucks: [] // Ensure 'pucks' array exists
};
const [newDewar, setNewDewar] = useState(initialNewDewarState);
@ -141,36 +149,80 @@ const SpreadsheetTable = ({
'dewarname': 0,
'puckname': 1,
'pucktype': 2,
// Add other fields as needed
'crystalname': 3,
'positioninpuck': 4,
'priority': 5,
'comments': 6,
'directory': 7,
'proteinname': 8,
'oscillation': 9,
'aperture': 10,
'exposure': 11,
'totalrange': 12,
'transmission': 13,
'dose': 14,
'targetresolution': 15,
'datacollectiontype': 16,
'processingpipeline': 17,
'spacegroupnumber': 18,
'cellparameters': 19,
'rescutkey': 20,
'rescutvalue': 21,
'userresolution': 22,
'pdbid': 23,
'autoprocfull': 24,
'procfull': 25,
'adpenabled': 26,
'noano': 27,
'ffcscampaign': 28,
'trustedhigh': 29,
'autoprocextraparams': 30,
'chiphiangles': 31
};
const createDewarsFromSheet = async (data, contactPerson, returnAddress) => {
const checkIfDewarExists = async (dewarName) => {
if (!selectedShipment) return null;
try {
const shipDewars = await ShipmentsService.getDewarsByShipmentIdShipmentsShipmentIdDewarsGet(selectedShipment.id);
return shipDewars.find((d) => d.dewar_name === dewarName);
} catch (error) {
console.error('Failed to fetch existing dewars:', error);
return null;
}
};
const createOrUpdateDewarsFromSheet = async (data, contactPerson, returnAddress) => {
if (!contactPerson?.id || !returnAddress?.id) {
console.error('contact_person_id or return_address_id is missing');
return null;
}
const dewars = new Map();
const puckPositionMap = new Map();
const dewarsToReplace = [];
const dewarNameIdx = fieldToCol['dewarname'];
const puckNameIdx = fieldToCol['puckname'];
const puckTypeIdx = fieldToCol['pucktype'];
const sampleNameIdx = fieldToCol['crystalname'];
const samplePositionIdx = fieldToCol['positioninpuck'];
let puckPositionInDewar = 1;
for (let rowIndex = 0; rowIndex < data.length; rowIndex++) {
const row = data[rowIndex];
for (const row of data) {
if (!row.data) {
console.error(`Row data is missing`);
console.error('Row data is missing');
continue;
}
const dewarName = typeof row.data[dewarNameIdx] === 'string' ? row.data[dewarNameIdx].trim() : null;
const puckName = typeof row.data[puckNameIdx] === 'string' ? row.data[puckNameIdx].trim() : null;
const puckName = row.data[puckNameIdx] !== undefined && row.data[puckNameIdx] !== null ? String(row.data[puckNameIdx]).trim() : null;
const puckType = typeof row.data[puckTypeIdx] === 'string' ? row.data[puckTypeIdx] : 'Unipuck';
const sampleName = typeof row.data[sampleNameIdx] === 'string' ? row.data[sampleNameIdx].trim() : null;
const samplePosition = row.data[samplePositionIdx] !== undefined && row.data[samplePositionIdx] !== null ? Number(row.data[samplePositionIdx]) : null;
console.log(`Processing Dewar: ${dewarName}, Puck: ${puckName}, Type: ${puckType}`);
if (dewarName) {
if (dewarName && puckName) {
let dewar;
if (!dewars.has(dewarName)) {
dewar = {
@ -181,44 +233,116 @@ const SpreadsheetTable = ({
pucks: []
};
dewars.set(dewarName, dewar);
puckPositionInDewar = 1;
console.log(`Created new dewar: ${dewarName}`);
puckPositionMap.set(dewarName, new Map());
// Check if the dewar exists in the shipment
const existingDewar = await checkIfDewarExists(dewarName);
if (existingDewar) {
dewarsToReplace.push(existingDewar);
}
} else {
dewar = dewars.get(dewarName);
puckPositionInDewar++;
console.log(`Found existing dewar: ${dewarName}`);
}
const puck = {
puck_name: puckName || 'test', // Fixed puck name
puck_type: puckType || 'Unipuck', // Fixed puck type
puck_position_in_dewar: puckPositionInDewar
};
dewar.pucks.push(puck);
let puckPositions = puckPositionMap.get(dewarName);
if (!puckPositions.has(puckName)) {
puckPositions.set(puckName, puckPositions.size + 1);
}
const puckPosition = puckPositions.get(puckName);
console.log(`Added puck: ${JSON.stringify(puck)}`);
let puck = dewar.pucks.find(p => p.puck_name === puckName);
if (!puck) {
puck = {
puck_name: puckName,
puck_type: puckType,
puck_location_in_dewar: puckPosition,
samples: []
};
dewar.pucks.push(puck);
}
const sample = {
sample_name: sampleName,
position: samplePosition,
results: null // Placeholder for results field
};
if (isNaN(sample.position)) {
console.error(`Invalid sample position for sample ${sample.sample_name} in puck ${puckName}`);
} else {
puck.samples.push(sample);
}
} else {
console.error('Dewar name is missing in the row');
if (!dewarName) {
console.error(`Dewar name is missing in row ${rowIndex}`);
}
if (!puckName) {
console.error(`Puck name is missing in row ${rowIndex}`);
}
}
}
const dewarsArray = Array.from(dewars.values());
// Save dewars array for later use in handleConfirmUpdate
setDewarsToCreate(dewars);
if (dewarsArray.length > 0 && dewarsToReplace.length > 0) {
setDewarsToReplace(dewarsToReplace);
setShowUpdateDialog(true);
} else {
await handleDewarCreation(dewarsArray);
}
};
const handleConfirmUpdate = async () => {
if (dewarsToReplace.length === 0) return;
try {
for (const dewar of dewarsToReplace) {
await DewarsService.deleteDewarDewarsDewarIdDelete(dewar.id);
}
const dewarsArray = Array.from(dewarsToCreate.values());
await handleDewarCreation(dewarsArray);
console.log('Dewars replaced successfully');
} catch (error) {
console.error('Error replacing dewar', error);
if (error instanceof ApiError && error.body) {
console.error('Validation errors:', error.body.detail);
} else {
console.error('Unexpected error:', error);
}
}
setShowUpdateDialog(false);
setDewarsToReplace([]);
setDewarsToCreate(new Map());
};
const handleCancelUpdate = () => {
setShowUpdateDialog(false);
setDewarsToReplace([]);
setDewarsToCreate(new Map());
};
const handleDewarCreation = async (dewarsArray) => {
for (const dewar of dewarsArray) {
try {
// Call to create the dewar
const createdDewar = await DewarsService.createDewarDewarsPost(dewar);
console.log(`Created dewar: ${createdDewar.id}`);
if (!dewar.pucks || dewar.pucks.length === 0) {
console.error(`Dewar ${dewar.dewar_name} does not have any pucks.`);
continue;
}
const createdDewar = await DewarsService.createDewarDewarsPost(dewar);
// Add dewar to the shipment if created successfully
if (createdDewar && selectedShipment) {
await ShipmentsService.addDewarToShipmentShipmentsShipmentIdAddDewarPost(
selectedShipment.id,
createdDewar.id
);
console.log(`Added dewar to shipment: ${createdDewar.id}`);
console.log(`Dewar ${createdDewar.dewar_name} with ID ${createdDewar.id} created and added to the shipment.`);
}
} catch (error) {
console.error(`Error adding dewar`, error);
console.error('Error adding dewar', error);
if (error instanceof ApiError && error.body) {
console.error('Validation errors:', error.body.detail);
} else {
@ -226,8 +350,6 @@ const SpreadsheetTable = ({
}
}
}
return dewarsArray;
};
const handleSubmit = async () => {
@ -241,18 +363,12 @@ const SpreadsheetTable = ({
setIsSubmitting(true);
console.log('All data is valid. Proceeding with submission...');
const processedDewars = await createDewarsFromSheet(
await createOrUpdateDewarsFromSheet(
raw_data,
selectedShipment?.contact_person,
selectedShipment?.return_address
);
if (processedDewars && processedDewars.length > 0) {
console.log('Dewars processed successfully.');
} else {
console.error('No valid dewars were created.');
}
setIsSubmitting(false);
} else {
console.log('There are validation errors in the dataset. Please correct them before submission.');
@ -270,22 +386,12 @@ const SpreadsheetTable = ({
});
});
const buffer = await workbook.xlsx.writeBuffer();
const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
saveAs(blob, 'corrected_data.xlsx');
workbook.xlsx.writeBuffer().then((buffer) => {
const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
saveAs(blob, 'corrected_spreadsheet.xlsx');
});
};
useEffect(() => {
console.log('Raw data:', raw_data);
console.log('Errors:', localErrors);
console.log('Headers:', headers);
}, [raw_data, localErrors, headers]);
if (!raw_data || !headers) {
return <div>Loading...</div>;
}
return (
<TableContainer component={Paper}>
<Table>
@ -323,21 +429,25 @@ const SpreadsheetTable = ({
return (
<TableCell key={colIndex} align="center">
<Tooltip title={errorMessage || ""} arrow disableHoverListener={!isInvalid}>
<TextField
value={editingValue !== undefined ? editingValue : cellValue}
onChange={(e) => setEditingCell({ ...editingCell, [`${rowIndex}-${colIndex}`]: e.target.value })}
onKeyDown={(e) => {
if (e.key === "Enter") {
handleCellEdit(rowIndex, colIndex);
}
}}
onBlur={() => handleCellBlur(rowIndex, colIndex)}
error={isInvalid}
fullWidth
variant="outlined"
size="small"
disabled={isReadonly}
/>
{isInvalid ? (
<TextField
value={editingValue !== undefined ? editingValue : cellValue}
onChange={(e) => setEditingCell({ ...editingCell, [`${rowIndex}-${colIndex}`]: e.target.value })}
onKeyDown={(e) => {
if (e.key === "Enter") {
handleCellEdit(rowIndex, colIndex);
}
}}
onBlur={() => handleCellBlur(rowIndex, colIndex)}
error={isInvalid}
fullWidth
variant="outlined"
size="small"
disabled={isReadonly}
/>
) : (
cellValue
)}
</Tooltip>
</TableCell>
);
@ -346,6 +456,28 @@ const SpreadsheetTable = ({
))}
</TableBody>
</Table>
<Dialog
open={showUpdateDialog}
onClose={handleCancelUpdate}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">Replace Dewars</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
The following dewars already exist: {dewarsToReplace.map(dewar => dewar.dewar_name).join(', ')}. Would you like to replace them?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleCancelUpdate} color="primary">
Cancel
</Button>
<Button onClick={handleConfirmUpdate} color="primary" autoFocus>
Replace
</Button>
</DialogActions>
</Dialog>
</TableContainer>
);
};