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)
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]
class DewarCreate(BaseModel):
dewar_name: str = Field(..., alias="dewarname")
tracking_number: Optional[str]
status: Optional[str] = None
contact_person_id: Optional[int] = None
return_address_id: Optional[int] = None
pucks: List[PuckCreate] = []
class DewarCreate(DewarBase):
pass
class Dewar(DewarBase):

View File

@ -8,33 +8,41 @@ from app.sample_models import SpreadsheetModel
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
class SpreadsheetImportError(Exception):
pass
class SampleSpreadsheetImporter:
def __init__(self):
self.filename = None
self.model = 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:
return None
if expected_type == str:
# Ensure value is converted to string and stripped of whitespace
return str(value).strip()
if expected_type in [float, int]:
try:
return expected_type(value)
except ValueError:
except (ValueError, TypeError):
# If conversion fails, return None
return None
if isinstance(value, str):
try:
# Handle numeric strings
if '.' in value:
return float(value)
else:
return int(value)
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
def import_spreadsheet(self, file):
@ -209,4 +217,4 @@ class SampleSpreadsheetImporter:
self.model = model
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 updatedShipment = isSelected ? null : shipment;
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);
};

View File

@ -13,10 +13,16 @@ import {
Button,
Box
} from '@mui/material';
import { SpreadsheetService, ShipmentsService } from '../../openapi';
import { SpreadsheetService, ShipmentsService, DewarsService, ApiError } from '../../openapi';
import * as ExcelJS from 'exceljs';
import { saveAs } from 'file-saver';
const HeaderMapping = {
'dewarname': 'dewar_name',
'trackingnumber': 'tracking_number',
'status': 'status'
};
const SpreadsheetTable = ({
raw_data,
errors,
@ -24,11 +30,37 @@ const SpreadsheetTable = ({
setRawData,
onCancel,
fileBlob,
shipmentId // Accept the shipmentId
selectedShipment
}) => {
const [localErrors, setLocalErrors] = useState(errors || []);
const [editingCell, setEditingCell] = useState({});
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 errorMap = new Map();
@ -112,78 +144,122 @@ const SpreadsheetTable = ({
const allCellsValid = () => nonEditableCells.size === raw_data.length * headers.length;
const handleSubmit = async () => {
if (allCellsValid()) {
console.log('All data is valid. Proceeding with submission...');
const processedData = createPayload(raw_data);
const fieldToCol = {
'dewarname': 0,
'puckname': 1,
'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 response = await ShipmentsService.addDewarPuckSampleToShipmentShipmentsShipmentIdAddDewarPuckSamplePost(shipmentId, processedData);
console.log('Shipment processed successfully:', response);
const createDewarsFromSheet = async (data, contactPerson, returnAddress) => {
if (!contactPerson?.id || !returnAddress?.id) {
console.error('contact_person_id or return_address_id is missing');
return null;
}
// Handle success actions, e.g., display notification, reset state, etc.
} catch (error) {
console.error('Error processing shipment:', error);
// Handle error actions, e.g., display notification, etc.
const dewars = new Map(); // Use a Map to prevent duplicates
for (const row of data) {
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 {
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 workbook = new ExcelJS.Workbook();
await workbook.xlsx.load(fileBlob);
@ -227,7 +303,7 @@ const SpreadsheetTable = ({
<Button variant="contained" color="secondary" onClick={onCancel}>
Cancel
</Button>
<Button variant="contained" color="primary" onClick={handleSubmit} disabled={!allCellsValid()}>
<Button variant="contained" color="primary" onClick={handleSubmit} disabled={!allCellsValid() || isSubmitting}>
Submit
</Button>
<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 }))}
onCancel={handleCancel}
fileBlob={fileBlob} // Pass the original file blob
shipmentId={selectedShipment?.id} // Pass the selected shipment ID
selectedShipment={selectedShipment} // Pass the selected shipment ID
/>
</Modal>
)}