Update beamtime assignment logic for pucks and samples

Simplified and unified beamtime assignment handling for pucks and samples in the backend. Enhanced the frontend to display detailed assignment state, including shift, date, and beamline, for both pucks and dewars. This ensures consistent and accurate state management across the application.
This commit is contained in:
GotthardG 2025-05-07 09:39:40 +02:00
parent 9e5734f060
commit e341459590
3 changed files with 89 additions and 30 deletions

View File

@ -595,14 +595,23 @@ async def assign_beamtime_to_dewar(
db: Session = Depends(get_db),
):
dewar = db.query(DewarModel).filter(DewarModel.id == dewar_id).first()
if not dewar: # <- Move check earlier!
if not dewar:
raise HTTPException(status_code=404, detail="Dewar not found")
if beamtime_id == 0:
dewar.beamtime_id = None
else:
dewar.beamtime_id = beamtime_id
dewar.beamtime_id = None if beamtime_id == 0 else beamtime_id
db.commit()
db.refresh(dewar)
for puck in dewar.pucks:
puck.beamtime_id = None if beamtime_id == 0 else beamtime_id
for sample in puck.samples:
has_sample_event = (
db.query(SampleEvent).filter(SampleEvent.sample_id == sample.id).count()
> 0
)
if not has_sample_event:
sample.beamtime_id = None if beamtime_id == 0 else beamtime_id
db.commit()
return {"status": "success", "dewar_id": dewar.id, "beamtime_id": beamtime_id}

View File

@ -20,6 +20,7 @@ from app.models import (
Sample as SampleModel,
LogisticsEvent as LogisticsEventModel,
Dewar as DewarModel,
SampleEvent,
)
from app.dependencies import get_db
import logging
@ -669,10 +670,16 @@ async def assign_beamtime_to_puck(
puck = db.query(PuckModel).filter(PuckModel.id == puck_id).first()
if not puck:
raise HTTPException(status_code=404, detail="Puck not found")
if beamtime_id == 0:
puck.beamtime_id = None
else:
puck.beamtime_id = beamtime_id
puck.beamtime_id = None if beamtime_id == 0 else beamtime_id
db.commit()
db.refresh(puck)
# Update samples
for sample in puck.samples:
has_sample_event = (
db.query(SampleEvent).filter(SampleEvent.sample_id == sample.id).count() > 0
)
if not has_sample_event:
sample.beamtime_id = None if beamtime_id == 0 else beamtime_id
db.commit()
return {"status": "success", "puck_id": puck.id, "beamtime_id": beamtime_id}

View File

@ -268,12 +268,11 @@ const Calendar: React.FC = () => {
return;
}
// Find out if it's already assigned to this shift!
// Get current association state
const prev = eventAssociations[selectedEventId] || { dewars: [], pucks: [] };
const isAssigned = prev.pucks.includes(puckId);
if (!isAssigned) {
// Assign it immediately
assignPuckToBeamtime(Number(puckId), Number(beamtimeId))
.then(() => {
setEventAssociations(prevAssoc => {
@ -291,7 +290,6 @@ const Calendar: React.FC = () => {
console.error("Failed to assign puck to beamtime", e);
});
} else {
// Unassign (patch to None) immediately
unassignPuckFromBeamtime(Number(puckId))
.then(() => {
setEventAssociations(prevAssoc => {
@ -387,10 +385,8 @@ const Calendar: React.FC = () => {
<h4>Select Dewars</h4>
<ul>
{shipments.map(dewar => {
// Are *all* pucks assigned to this event? (Assigned at Dewar level)
const thisEvent = eventAssociations[selectedEventId] || {dewars: [], pucks: []};
const thisEvent = eventAssociations[selectedEventId] || { dewars: [], pucks: [] };
const dewarAssigned = thisEvent.dewars.includes(dewar.id);
const puckAssignments = thisEvent.pucks;
return (
<li key={dewar.id}>
@ -405,8 +401,20 @@ const Calendar: React.FC = () => {
{!dewarAssigned && dewar.pucks && dewar.pucks.length > 0 && (
<ul>
{(dewar.pucks || []).map(puck => {
const isAssigned =
!!eventAssociations[selectedEventId]?.pucks.includes(puck.id);
// Find eventId for this puck, if assigned
const associatedEventId = Object.keys(eventAssociations).find(eid =>
eventAssociations[eid]?.pucks.includes(puck.id)
);
const associatedEvent = associatedEventId
? events.find(ev => ev.id === associatedEventId)
: null;
const associatedShift = associatedEvent?.beamtime_shift;
const associatedDate = associatedEvent?.start;
const associatedBeamline = associatedEvent?.beamline;
const currentShift = eventDetails?.beamtime_shift;
const isAssignedToCurrentShift = associatedShift && currentShift && associatedShift === currentShift;
return (
<div key={puck.id} style={{ display: 'flex', alignItems: 'center' }}>
@ -415,32 +423,67 @@ const Calendar: React.FC = () => {
type="button"
style={{
marginLeft: 8,
background: isAssigned ? '#4CAF50' : '#e0e0e0',
color: isAssigned ? 'white' : 'black',
border: isAssigned ? '1px solid #388e3c' : '1px solid #bdbdbd',
background: isAssignedToCurrentShift ? '#4CAF50' : (associatedShift ? '#B3E5B3' : '#e0e0e0'),
color: isAssignedToCurrentShift ? 'white' : 'black',
border: isAssignedToCurrentShift ? '1px solid #388e3c' : '1px solid #bdbdbd',
borderRadius: 4,
padding: '4px 10px',
cursor: 'pointer',
transition: 'background 0.2s',
}}
onClick={() => handlePuckAssignment(dewar.id, puck.id)}
onClick={() => handlePuckAssignment(puck.id)}
>
{isAssigned ? puck.puck_name : puck.puck_name}
{puck.puck_name}
</button>
{/* Optionally show assignment info, as before */}
{findAssociatedEventForPuck(puck.id) && (
<span style={{marginLeft: 8, color: 'green'}}> Assigned to shift: {findAssociatedEventForPuck(puck.id)}</span>
{associatedEvent && (
<span
style={{
marginLeft: 8,
color: isAssignedToCurrentShift ? 'green' : '#388e3c',
fontWeight: isAssignedToCurrentShift ? 700 : 400
}}
>
Assigned to shift: {associatedShift}
{associatedDate && (
<> on {new Date(associatedDate).toLocaleDateString()}</>
)}
{associatedBeamline && (
<> ({associatedBeamline})</>
)}
</span>
)}
</div>
);
})}
</ul>
)}
{/* Show associated shift, if any */}
{findAssociatedEventForDewar(dewar.id) && (
<span style={{marginLeft: 8, color: 'green'}}> Assigned to shift: {findAssociatedEventForDewar(dewar.id)}</span>
)}
{/* Show associated shift, date, and beamline for Dewar, if any */}
{(() => {
const associatedEventId = Object.keys(eventAssociations).find(eid =>
eventAssociations[eid]?.dewars.includes(dewar.id)
);
const associatedEvent = associatedEventId
? events.find(ev => ev.id === associatedEventId)
: null;
const associatedShift = associatedEvent?.beamtime_shift;
const associatedDate = associatedEvent?.start;
const associatedBeamline = associatedEvent?.beamline;
const currentShift = eventDetails?.beamtime_shift;
const isAssignedToCurrentShift = associatedShift && currentShift && associatedShift === currentShift;
return associatedEvent && (
<span style={{ marginLeft: 8, color: isAssignedToCurrentShift ? 'green' : '#388e3c', fontWeight: isAssignedToCurrentShift ? 700 : 400 }}>
Assigned to shift: {associatedShift}
{associatedDate && (
<> on {new Date(associatedDate).toLocaleDateString()}</>
)}
{associatedBeamline && (
<> ({associatedBeamline})</>
)}
</span>
);
})()}
</li>
);
})}