now creating dewars from spreadsheet

This commit is contained in:
GotthardG 2024-11-11 21:37:59 +01:00
parent 52fe68b2bc
commit 5e6eb40033
6 changed files with 207 additions and 79 deletions

View File

@ -263,3 +263,48 @@ async def add_dewar_puck_sample_to_shipment(shipment_id: int, payload: DewarCrea
db.refresh(shipment) db.refresh(shipment)
return shipment return shipment
@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: ShipmentCreate, 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")
for dewar_data in payload.dewars:
dewar = DewarModel(
shipment_id=shipment_id,
dewar_name=dewar_data.dewar_name, # Ensure this field aligns with the frontend
tracking_number=dewar_data.tracking_number,
status=dewar_data.status,
contact_person_id=dewar_data.contact_person_id,
return_address_id=dewar_data.return_address_id,
)
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.crystalname,
position=sample_data.positioninpuck,
data_collection_parameters=sample_data.data_collection_parameters
)
db.add(sample)
db.commit()
db.refresh(sample)
db.refresh(shipment)
return shipment

View File

@ -154,13 +154,8 @@ class DewarBase(BaseModel):
return_address_id: Optional[int] return_address_id: Optional[int]
class DewarCreate(BaseModel): class DewarCreate(DewarBase):
dewar_name: str = Field(..., alias="dewarname") pass
tracking_number: Optional[str]
status: Optional[str] = None
contact_person_id: Optional[int] = None
return_address_id: Optional[int] = None
pucks: List[PuckCreate] = []
class Dewar(DewarBase): class Dewar(DewarBase):

View File

@ -8,33 +8,41 @@ from app.sample_models import SpreadsheetModel
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class SpreadsheetImportError(Exception): class SpreadsheetImportError(Exception):
pass pass
class SampleSpreadsheetImporter: class SampleSpreadsheetImporter:
def __init__(self): def __init__(self):
self.filename = None self.filename = None
self.model = None self.model = None
def _clean_value(self, value, expected_type=None): def _clean_value(self, value, expected_type=None):
"""Clean value by converting it to the expected type and stripping whitespace for strings.""" """Clean value by converting it to the expected type and handle edge cases."""
if value is None: if value is None:
return None return None
if expected_type == str: if expected_type == str:
# Ensure value is converted to string and stripped of whitespace
return str(value).strip() return str(value).strip()
if expected_type in [float, int]: if expected_type in [float, int]:
try: try:
return expected_type(value) return expected_type(value)
except ValueError: except (ValueError, TypeError):
# If conversion fails, return None
return None return None
if isinstance(value, str): if isinstance(value, str):
try: try:
# Handle numeric strings
if '.' in value: if '.' in value:
return float(value) return float(value)
else: else:
return int(value) return int(value)
except ValueError: except ValueError:
return value.strip() pass
# In case of failure, return the stripped string
return value.strip()
# If no expected type or value type match, return the original value
return value return value
def import_spreadsheet(self, file): def import_spreadsheet(self, file):
@ -209,4 +217,4 @@ class SampleSpreadsheetImporter:
self.model = model self.model = model
logger.info(f"Finished processing {len(model)} records with {len(errors)} errors") logger.info(f"Finished processing {len(model)} records with {len(errors)} errors")
return self.model, errors, raw_data, headers # Include headers in the response return self.model, errors, raw_data, headers # Include headers in the response

View File

@ -61,6 +61,10 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
const isSelected = selectedShipment?.id === shipment.id; const isSelected = selectedShipment?.id === shipment.id;
const updatedShipment = isSelected ? null : shipment; const updatedShipment = isSelected ? null : shipment;
console.log("Shipment selected:", updatedShipment); // debug log console.log("Shipment selected:", updatedShipment); // debug log
if (updatedShipment) {
console.log("Contact Person ID:", updatedShipment.contact_person_id);
console.log("Return Address ID:", updatedShipment.return_address_id);
}
selectShipment(updatedShipment); selectShipment(updatedShipment);
}; };

View File

@ -13,10 +13,16 @@ import {
Button, Button,
Box Box
} from '@mui/material'; } from '@mui/material';
import { SpreadsheetService, ShipmentsService } from '../../openapi'; import { SpreadsheetService, ShipmentsService, DewarsService, ApiError } from '../../openapi';
import * as ExcelJS from 'exceljs'; import * as ExcelJS from 'exceljs';
import { saveAs } from 'file-saver'; import { saveAs } from 'file-saver';
const HeaderMapping = {
'dewarname': 'dewar_name',
'trackingnumber': 'tracking_number',
'status': 'status'
};
const SpreadsheetTable = ({ const SpreadsheetTable = ({
raw_data, raw_data,
errors, errors,
@ -24,11 +30,37 @@ const SpreadsheetTable = ({
setRawData, setRawData,
onCancel, onCancel,
fileBlob, fileBlob,
shipmentId // Accept the shipmentId selectedShipment
}) => { }) => {
const [localErrors, setLocalErrors] = useState(errors || []); const [localErrors, setLocalErrors] = useState(errors || []);
const [editingCell, setEditingCell] = useState({}); const [editingCell, setEditingCell] = useState({});
const [nonEditableCells, setNonEditableCells] = useState(new Set()); const [nonEditableCells, setNonEditableCells] = useState(new Set());
const [isSubmitting, setIsSubmitting] = useState(false);
const initialNewDewarState = {
number_of_pucks: 0,
number_of_samples: 0,
ready_date: null,
shipping_date: null,
arrival_date: null,
returning_date: null,
qrcode: 'N/A',
contact_person_id: selectedShipment?.contact_person?.id,
return_address_id: selectedShipment?.return_address?.id,
dewar_name: '',
tracking_number: 'UNKNOWN',
status: 'In preparation',
};
const [newDewar, setNewDewar] = useState(initialNewDewarState);
useEffect(() => {
setNewDewar((prev) => ({
...prev,
contact_person_id: selectedShipment?.contact_person?.id,
return_address_id: selectedShipment?.return_address?.id
}));
}, [selectedShipment]);
const generateErrorMap = (errorsList) => { const generateErrorMap = (errorsList) => {
const errorMap = new Map(); const errorMap = new Map();
@ -112,78 +144,122 @@ const SpreadsheetTable = ({
const allCellsValid = () => nonEditableCells.size === raw_data.length * headers.length; const allCellsValid = () => nonEditableCells.size === raw_data.length * headers.length;
const handleSubmit = async () => { const fieldToCol = {
if (allCellsValid()) { 'dewarname': 0,
console.log('All data is valid. Proceeding with submission...'); 'puckname': 1,
const processedData = createPayload(raw_data); 'pucktype': 2,
'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,
};
try { const createDewarsFromSheet = async (data, contactPerson, returnAddress) => {
const response = await ShipmentsService.addDewarPuckSampleToShipmentShipmentsShipmentIdAddDewarPuckSamplePost(shipmentId, processedData); if (!contactPerson?.id || !returnAddress?.id) {
console.log('Shipment processed successfully:', response); console.error('contact_person_id or return_address_id is missing');
return null;
}
// Handle success actions, e.g., display notification, reset state, etc. const dewars = new Map(); // Use a Map to prevent duplicates
} catch (error) {
console.error('Error processing shipment:', error); for (const row of data) {
// Handle error actions, e.g., display notification, etc. if (!row.data) {
console.error(`Row data is missing`);
continue;
} }
const dewarNameIdx = fieldToCol['dewarname'];
const dewarName = row.data[dewarNameIdx]?.trim();
if (dewarName && !dewars.has(dewarName)) {
const newDewarToPost = {
...initialNewDewarState,
dewar_name: dewarName,
tracking_number: row.data[fieldToCol['trackingnumber']] || "UNKNOWN",
status: row.data[fieldToCol['status']] || "In preparation",
contact_person_id: contactPerson.id,
return_address_id: returnAddress.id,
};
try {
const createdDewar = await DewarsService.createDewarDewarsPost(newDewarToPost);
if (createdDewar && selectedShipment) {
const updatedShipment = await ShipmentsService.addDewarToShipmentShipmentsShipmentIdAddDewarPost(
selectedShipment.id,
createdDewar.id
);
dewars.set(dewarName, updatedShipment); // Track the added dewar
}
} catch (error) {
console.error(`Error adding dewar for row: ${row.row_num}`, error);
if (error instanceof ApiError && error.body) {
console.error('Validation errors:', error.body.detail);
} else {
console.error('Unexpected error:', error);
}
}
} else if (!dewarName) {
console.error('Dewar name is missing in the row');
}
}
return Array.from(dewars.values());
};
const handleSubmit = async () => {
if (isSubmitting) return; // Prevent multiple submissions
if (!headers || headers.length === 0) {
console.error('Cannot submit, headers are not defined or empty');
return;
}
if (allCellsValid()) {
setIsSubmitting(true);
console.log('All data is valid. Proceeding with submission...');
const processedDewars = await createDewarsFromSheet(
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 { } else {
console.log('There are validation errors in the dataset. Please correct them before submission.'); console.log('There are validation errors in the dataset. Please correct them before submission.');
} }
}; };
const createPayload = (data) => {
const allowedFields = [
'priority', 'comments', 'directory', 'proteinname', 'oscillation', 'aperture',
'exposure', 'totalrange', 'transmission', 'dose', 'targetresolution', 'datacollectiontype',
'processingpipeline', 'spacegroupnumber', 'cellparameters', 'rescutkey', 'rescutvalue',
'userresolution', 'pdbid', 'autoprocfull', 'procfull', 'adpenabled', 'noano',
'ffcscampaign', 'trustedhigh', 'autoprocextraparams', 'chiphiangles'
];
let dewars = {};
data.forEach((row) => {
const dewarname = row.data[headers.indexOf('dewarname')];
const puckname = row.data[headers.indexOf('puckname')];
const crystalname = row.data[headers.indexOf('crystalname')];
const positioninpuck = row.data[headers.indexOf('positioninpuck')];
if (!dewars[dewarname]) {
dewars[dewarname] = {
dewarname: dewarname,
pucks: {}
};
}
if (!dewars[dewarname].pucks[puckname]) {
dewars[dewarname].pucks[puckname] = {
puckname: puckname,
samples: []
};
}
const dataCollectionParams = {};
headers.forEach((header, index) => {
if (allowedFields.includes(header)) {
dataCollectionParams[header] = row.data[index];
}
});
dewars[dewarname].pucks[puckname].samples.push({
crystalname: crystalname,
positioninpuck: positioninpuck,
data_collection_parameters: dataCollectionParams
});
});
const dewarsList = Object.values(dewars).map(dewar => ({
dewarname: dewar.dewarname,
pucks: Object.values(dewar.pucks)
}));
return { dewars: dewarsList };
};
const downloadCorrectedSpreadsheet = async () => { const downloadCorrectedSpreadsheet = async () => {
const workbook = new ExcelJS.Workbook(); const workbook = new ExcelJS.Workbook();
await workbook.xlsx.load(fileBlob); await workbook.xlsx.load(fileBlob);
@ -227,7 +303,7 @@ const SpreadsheetTable = ({
<Button variant="contained" color="secondary" onClick={onCancel}> <Button variant="contained" color="secondary" onClick={onCancel}>
Cancel Cancel
</Button> </Button>
<Button variant="contained" color="primary" onClick={handleSubmit} disabled={!allCellsValid()}> <Button variant="contained" color="primary" onClick={handleSubmit} disabled={!allCellsValid() || isSubmitting}>
Submit Submit
</Button> </Button>
<Button variant="contained" onClick={downloadCorrectedSpreadsheet} disabled={!allCellsValid()}> <Button variant="contained" onClick={downloadCorrectedSpreadsheet} disabled={!allCellsValid()}>

View File

@ -166,7 +166,7 @@ const UploadDialog: React.FC<UploadDialogProps> = ({ open, onClose, selectedShip
setRawData={(newRawData) => setFileSummary((prevSummary) => ({ ...prevSummary, raw_data: newRawData }))} setRawData={(newRawData) => setFileSummary((prevSummary) => ({ ...prevSummary, raw_data: newRawData }))}
onCancel={handleCancel} onCancel={handleCancel}
fileBlob={fileBlob} // Pass the original file blob fileBlob={fileBlob} // Pass the original file blob
shipmentId={selectedShipment?.id} // Pass the selected shipment ID selectedShipment={selectedShipment} // Pass the selected shipment ID
/> />
</Modal> </Modal>
)} )}