now creating dewars from spreadsheet
This commit is contained in:
parent
52fe68b2bc
commit
5e6eb40033
@ -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
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
@ -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()}>
|
||||
|
@ -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>
|
||||
)}
|
||||
|
Loading…
x
Reference in New Issue
Block a user